building smart async functions for mobile
DESCRIPTION
TRANSCRIPT
BUILDING SMART ASYNC FUNCTIONS FOR MOBILE
Glan ThomasMountain View JavaScript Meetup Group
Aug 10, 2011
TYPICAL ASYNC FUNCTIONS
getJSON(url, [data,] [success(data, textStatus, jqXHR),] [dataType])
get(url, [data,] [success(data, textStatus, jqXHR),] [dataType])
post(url, [data,] [success(data, textStatus, jqXHR),] [dataType])
ASYNC PROBLEMS
• Requires an HTTP request
• Requires a network connection
• Cross domain origin
OBJECTIVES
•Minimize HTTP requests
•Make things work even if you are offline
• Avoid cluttering up the app’s controllers with online/offline conditionals
• Preserve original API signatures
getJSON( url, [data,] [success,] [cache,] [filter,] [keyboardcat] )
WHAT CAN WE DO?
•Cache - Keep a local copy of the data
•Queue - For sending to the server later
•Merge - Combine requests into one
• Filter - Transform the incoming data
•AB Testing - Provide different data for different users
•Versioning - Making sure you hit the right endpoints
TECHNIQUES
• Interfaces - for utility objects
• Decorators - for augmenting async functions
• Delegation - to utility object
INTERFACES
Cache Interface
• get(key)
• set(key, value)
• delete(key)
Queue Interface
• add(item)
• remove()
DECORATORS
WHAT JUST HAPPENED?
jQuery.get CacheDecorator getCached
var cache = new Cache(),
myGet = jQuery.get;
myGet = new CacheDecorator(myGet, cache);
myGet(url, successFunction);
USAGE
CACHE DECORATOR
var CacheDecorator = function (func, cache) { 'use strict'; return function (url, success) { var data = cache.get(url); if (data) { success(data, 'hit'); } else { func(url, function (data) { cache.set(url, data); if (typeof success === 'function') { success.apply(this, arguments); } }); } };};
STACKING
var cache = new Cache(), filter = new Filter(),
myGet = jQuery.get;
myGet = new FilterDecorator(myGet, filter);
myGet = new CacheDecorator(myGet, cache);
myGet(url, successFunction);
FILTER DECORATOR
var FilterDecorator = function (func, filter) { 'use strict'; return function (url, success) { func(url, function (data) { data = filter(url, data); if (typeof success === 'function') { success.apply(this, arguments); } }); };};
QUEUE DECORATOR
var QueueDecorator = function (func, queue) { 'use strict'; return function (url, data, success) { queue.add({'func': func, 'args' : arguments}); success({}, 'queued'); };};
TAKEAWAYS
• Use decorators to enhance existing asynchronous functions without altering their signatures.
•Delegate functionality to dedicated utility objects (Caching/Queuing).
•Define interfaces for utility objects.
• Stack decorators to combine functionality.
SOME THINGS WE SKIPPED
• Cross domain origin
• Error and failure states
• Specific implementations of Cache and Queue classes (LocalStorage/SQLite)
• Enforcing of interfaces (see ‘Pro JavaScript Design Patterns’, Ross Harmes and Dustin Diaz, Apress, 2008)
QUESTIONS?
CACHE IMPLEMENTATIONCache.prototype = { set : function (key, value) { var package = {}; package.type = typeof value; package.value = (package.type === 'object') ? JSON.stringify(value) : value; localStorage[this._hash(key)] = JSON.stringify(package); }, get : function (key) { var package; if (this._exists(key)) { try { package = JSON.parse(localStorage[this._hash(key)]); } catch (e) { this.remove(key); return false; } return (package.type === 'object') ? JSON.parse(package.value) : package.value; } return false; }, remove : function (key) { if (this._exists(key)) { delete localStorage[this._hash(key)]; } }}
QUEUE IMPLEMENTATIONvar Queue = function() {};Queue.prototype = new Array();
Queue.prototype.add = function (item) {this.push(item);
};Queue.prototype.remove = function () { return this.shift();};Queue.prototype.goOnline = function () { var self = this, success; if(item = this[0]) { success = item.args[2]; item.args[2] = function () { success.apply(this, arguments); self.remove(); self.goOnline(); }; item.func.apply(this, item.args); }};
var queue = new Queue();
document.body.addEventListener("online", function () { queue.goOnline();}, false);
OFFLINE DECORATOR
var OfflineDecorator = function (func, offLine) { 'use strict'; return function (url, success) { if (offLine) { success({}, 'offline'); } else { func(url, function (data) { if (typeof success === 'function') { success.apply(this, arguments); } }); } };};