Rewrite code using promises - javascript

I have the following code that causes the two call to Webtrends to be cancelled (ie these two calls did not give a http 200 but a cancelled message in the network tab of the browser) when I call it
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
form[0].submit();
I rewrote this in this way to avoid this issue, as it seemed to me that the reason why the calls to Webtrends were being cancelled was because the form submit was making that happen so before calling submit on the form I wait two seconds.
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
var submit = function () {
setTimeout(function() {
form[0].submit();
}, 2000);
};
submit();
Question is, is there a better way, using promises or callbacks or whatever to do this?
The logUsage code is
(function ($, window) {
function Tracking() {
}
Tracking.prototype.chartTitle = function () {
return $('#chartNameInfo').text();
};
Tracking.prototype.hostName = function () {
return $('#trackingVars').data('host-name');
};
Tracking.prototype.page = function () {
return $('#trackingVars').data('page');
};
Tracking.prototype.currentUser = function () {
return window.config.userId;
};
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
var jsonString = '{"' + action + '"' + ':"1"}';
var jsonObj = JSON.parse(jsonString);
if (additionalTags == null) {
additionalTags = jsonObj;
}
else {
additionalTags = $.extend({}, additionalTags, jsonObj); //Append two JSON objects
}
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
dsci_uri: this.page()
}, additionalTags);
try {
WebTrends.multiTrack({ args: trackingargs });
} catch (e) {
console.log(e);
}
};
window.Tracking = new Tracking();
$(function() {
$('body').on('click', 'a[data-tracking-action]', function() {
window.Tracking.logUsage($(this).data('tracking-action'), $(this).data('tracking-resource'));
});
$(document).on('attempted-access-to-restricted-resource', function(event, href) {
window.Tracking.logUsage('unauthorisedResourceAccessUpsell', href.url);
});
});
})(jQuery, window);

With the extra information provided, I think I can now answer your question.
From WebTrends doc, you can add a finish callback to your WebTrends.MultiTrack call.
What you could do:
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
...
var finished = $.Deferred();
...
try {
WebTrends.multiTrack({ args: trackingargs, finish: function(){finished.resolve();}});
}
...
return finished;
}
and then in your code:
$.when(mercury.Tracking.logUsage("export", GetSelectedExportType(form)),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path))
.done(function(){
form[0].submit();
});
I have not tested this, but I think it should work. Hope it helps.
Explanations:
jQuery.when()
Description: Provides a way to execute callback functions based on one
or more objects, usually Deferred objects that represent asynchronous
events.
Basically, jQuery.when() will take one or more deferreds (which build promises) or promises and will return one promise that fulfills when they all fulfill. From there, we can choose to add handlers using th e .done() or .then() method to our promise, which will be called once or promise is fulfilled . (A promise represents the result of an asynchronous operation).
So, in the code above, I created a new deferred object in your logUsage method, and that method returns the deferred, so you can pass those deferreds to jQuery.when method and when they will be fulfilled (this is why I added the finish callback in your WebTrends.Multitrack call), the handler passed to deferred.done() will be executed.
I hope this is not too confusing, I'm not sure I'm explaining it correctly.

Not trying to steal Antoine's rep. His answer is essentially fine, but the ... sections can be fleshed out far more efficiently than in the question, plus a few other points for consideration.
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
try {
// you might as well wrap all the preamble in the try{}, just in case it it error-prone
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
//trackingargs can be defined efficiently as follows, avoiding the need for the variable `jsonObj` and the ugly JSON.parse().
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
'dsci_uri': this.page()
}, additionalTags || {}); // `additionalTags || {}` caters for missing or null additionalTags
trackingargs[action] = 1;//associative syntax gets around the limitation of object literals (and avoids the need for JSON.parse()!!!).
//to keep things tidy, return $.Deferred(fn).promise()
return $.Deferred(function(dfrd) {
WebTrends.multiTrack({
args: trackingargs,
finish: dfrd.resolve //no need for another function wrapper. `$.Deferred().resolve` and `$.Deferred().reject` are "detachable"
});
}).promise();//be sure to return a promise, not the entire Deferred.
} catch (e) {
console.log(e);
//Now, you should really ensure that a rejected promise is always returned.
return $.Deferred.reject(e).promise();//Surrogate re-throw.
}
};
see comments in code
As Tracking.prototype.logUsage can now return a rejected promise, and as you probably don't want .logUsage() failure to inhibit your form submission, you probably want to convert rejected promises to fulfilled.
$.when(
mercury.Tracking.logUsage("export", GetSelectedExportType(form)).then(null, function() {
return $.when();//resolved promise
}),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path).then(null, function() {
return $.when();//resolved promise
})
).done(function() {
form[0].submit();
});
It may seem to be an unnecessary complication to return a rejected promise then convert to success, however :
it is good practice to report asycnhronous failure in the form of a rejected promise, not simply log the error and return undefined.
window.Tracking.logUsage() may be called elsewhere in your code, where it is necessary to handle an error as an error.

Related

How to wrap an async package function?

I'm using the popular node library, got, to make simple GET requests to a JSON API.
I have a function that abstracts the request, like so:
function performRequest(url) {
got(url, {
json: true
}).then(function (response) {
return formatResponse(response.body);
}).catch(function (error) {
console.log(error.response.body);
});
}
formatResponse is a simple synchronous method that modifies the JSON returned from the API.
I would like to be able to call performRequest from another function and then use the return value (once resolved). Currently, as performRequest is not recognized as an async method, my code is calling it and then proceeding immediately.
function myBigFunction() {
var url = composeUrl();
var res = performRequest(url);
doMoreStuffWithResponse(res);
}
I know that I need to utilize a Promise, however, I'm always unclear as to how to use a Promise in conjunction with a built-in library function that is already using a Promise (like in this case).
I'm also completely open to the possibility that I'm going about this all wrong. In that case, I would appreciate some redirection.
Thank you for your time.
Understand what a Promise is. Its a value, you can treat it as such. In order to "read" the value, you pass a function to the Promise's then method. You don't need myBigFunction. Anything you want to run after the Promise resolves just needs to be passed to then:
var req = performRequest(composeURL());
req.then(doStuffWithResponse);
Now, I don't particularly care for this way although I do it fairly often. I prefer to have functions that take promises and invoke their then method:
var takesAPromise = function(p) {
return p.then(/* does stuff */);
};
Note that it returns the Promise of the completed task. But what I like even better is this ES6 one-liner:
let wrap = f => p => p.then(val => f.call(null, val));
Now you can wrap arbitrary functions to take Promises as input and return them as output. If Promises were a monad, this would be their bind function. Making it work seamlessly with functions of arbitrary arity is left as an exercise to the reader.
You'll always want to return a promise from your functions:
function performRequest(url) {
return got(url, {
//^^^^^^
json: true
}).then(function(response) {
return formatResponse(response.body);
}, function(error) {
throw new Error(error.response.body);
});
}
With this, you can wait for the result in your big functions using another then:
function myBigFunction() {
var url = composeUrl();
var promise = performRequest(url);
return promise.then(function(res) {
return doMoreStuffWithResponse(res);
});
}
or in short
function myBigFunction() {
return performRequest(composeUrl()).then(doMoreStuffWithResponse);
}
so that you can call it like
myBigFunction().catch(function(error) {
console.log(error.message);
});

New $.Deferred object with the old callbacks

Please forgive me if this is a stupid question. I have been trying for hours and my brain have just stopped working.
I have such system that consists of three AJAX calls. Server response of first call usually is a 200 Success; but second and third queries are fragile because they are image uploading, and on the server side, I have so much validation rules that client's images mostly fail.
window.AjaxCall = function () {
// to pass to $.ajax call later
this.args = arguments;
// xhr status
this.status = null;
// xhr results (jqXHR object and response)
this.xhrResponse = {};
this.dfr = new $.Deferred();
// to provide an easier interface
this.done = this.dfr.done;
this.fail = this.dfr.fail;
this.then = this.dfr.then;
};
AjaxCall.prototype.resetDfr = function () {
this.dfr = new $.Deferred();
};
AjaxCall.prototype.resolve = function () {
this.dfr.resolve(
this.xhrResponse.result,
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.reject = function () {
this.dfr.reject(
this.xhrResponse.jqXHR
);
this.resetDfr();
};
AjaxCall.prototype.query = function () {
var _this = this;
// if query hasn't run yet, or didn't return success, run it again
if (_this.status != 'OK') {
$.ajax.apply(_this, _this.args)
.done(function (result, textStatus, jqXHR) {
_this.xhrResponse.result = result;
_this.xhrResponse.jqXHR = jqXHR;
_this.resolve();
})
.fail(function (jqXHR) {
_this.xhrResponse.jqXHR = jqXHR;
_this.reject();
})
.always(function (a, b, c) {
var statusCode = (typeof c !== 'string'
? c
: a).status;
if (statusCode == 200) {
_this.status = 'OK';
}
});
}
// if query has been run successfully before, just skip to next
else {
_this.resolve();
}
return _this.dfr.promise();
};
AjaxCall class is as provided above, and I make the three consecutive calls like this:
var First = new AjaxCall('/'),
Second = new AjaxCall('/asd'),
Third = new AjaxCall('/qqq');
First.then(function () {
console.log('#1 done');
}, function() {
console.error('#1 fail');
});
Second.then(function () {
console.log('#2 done');
}, function() {
console.error('#2 fail');
});
Third.then(function () {
console.log('#3 done');
}, function() {
console.error('#3 fail');
});
var toRun = function () {
First.query()
.then(function () {
return Second.query();
})
.then(function () {
return Third.query()
});
};
$('button').click(function () {
toRun();
});
Those code are in a testing environment. And by testing environment, I mean a simple HTML page and basic server support for debugging.
Home page (/) always returns 200 Success.
/asd returns 404 Not Found for the first 3 times and 200 Success once as a pattern (i.e. three 404s -> one 200 -> three 404s -> one 200 -> three 404s -> ... ).
/qqq returns 404 Not Found all the time.
When I click the only button on the page, first query returns success and second fails as expected. When I click the button second time, first query skips because it was successful last time and second fails again, also as expected.
The problem here is:
before I used the resetDfr method because the dfr is alreay resolved or rejected, it doesn't react to resolve and reject methods anymore.
When I call the resetDfr method in the way I show in the example, dfr is able to get resolved or rejected again, but the callbacks of the old dfr are not binded with the new dfr object and I couldn't find a way to clone the old callbacks into the new dfr.
What would be your suggestion to accomplish what I'm trying to do here?
Promises represent a single value bound by time. You can't conceptually "reuse" a deferred or reset it - once it transitions it sticks. There are constructs that generalize promises to multiple values (like observables) but those are more complicated in this case - it's probably better to just use one deferred per request.
jQuery's AJAX already provides a promise interface. Your code is mostly redundant - you can and should consider using the existent tooling.
Let's look at $.get:
It already returns a promise so you don't need to create your own deferred.
It already uses the browser cache, unless your server prohibits HTTP caching or the browser refuses it only one request will be made to the server after a correct response arrived (assuming you did not explicitly pass {cache: false} to its parameters.
If making post requests you can use $.post or more generally $.ajax for arbitrary options.
This is how your code would roughly look like:
$("button").click(function(){
var first = $.get("/");
var second = first.then(function(){
return $.get("/asd");
});
var third = second.then(function(){
return $.get("/qqq");
});
});
The reason I put them in variables is so that you will be able to unwrap the result yourself later by doing first.then etc. It's quite possible to do this in a single chain too (but you lose access to previous values if you don't explicitly save them.
For the record - it wasn't a stupid question at all :)

Queuing promises

I use mbostock/queue for queuing few async operation. It is more to rate limit (UI generate few events, where the backend can process it slowly), and also to make sure they are processed sequentially. I use it like
function request(d, cb) {
//some async oper
add.then(function(){
cb(null, "finished ")
})
}
var addQ = queue(1);
addQ.defer(request) //called by few req at higher rates generated by UI
I already uses angular.js $q for async operation. So, do I have to use mbostock/queue, or can I build a queue out of $q (which is in spirit https://github.com/kriskowal/q)
Thanks.
Basic $q Chain Example
Yes you can build a chained queue using Angular's $q! Here is an example that shows you how you could use recursion to create a queue of any length. Each post happens in succession (one after another). The second post will not start until the first post has finished.
This can be helpful when writing to databases. If the database does not have it's own queue on the backend, and you make multiple writes at the same time, you may find that not all of your data is saved!
I have added a Plunkr example to demonstrate this code in action.
$scope.setData = function (data) {
// This array will hold the n-length queue
var promiseStack = [];
// Create a new promise (don't fire it yet)
function newPromise (key, data) {
return function () {
var deferred = $q.defer();
var postData = {};
postData[key] = data;
// Post the the data ($http returns a promise)
$http.post($scope.postPath, postData)
.then(function (response) {
// When the $http promise resolves, we also
// resolve the queued promise that contains it
deferred.resolve(response);
}, function (reason) {
deferred.reject(reason);
});
return deferred.promise;
};
}
// Loop through data creating our queue of promises
for (var key in data) {
promiseStack.push(newPromise(key, data[key]));
}
// Fire the first promise in the queue
var fire = function () {
// If the queue has remaining items...
return promiseStack.length &&
// Remove the first promise from the array
// and execute it
promiseStack.shift()()
// When that promise resolves, fire the next
// promise in our queue
.then(function () {
return fire();
});
};
// Begin the queue
return fire();
};
You can use a simple function to begin your queue. For the sake of this demonstration, I am passing an object full of keys to a function that will split these keys into individual posts, then POST them to Henry's HTTP Post Dumping Server. (Thanks Henry!)
$scope.beginQueue = function () {
$scope.setData({
a: 0,
b: 1,
/* ... all the other letters of the alphabet ... */
y: 24,
z: 25
}).then(function () {
console.log('Everything was saved!');
}).catch(function (reason) {
console.warn(reason);
});
};
Here is a link to the Plunkr example if you would like to try out this code.
The short answer is no, you don't need an extra library. Promise.then() is sufficiently "atomic". The long answer is: it's worth making a queue() function to keep code DRY. Bluebird-promises seems pretty complete, but here's something based on AngularJS's $q.
If I was making .queue() I'd want it to handle errors as well.
Here's an angular service factory, and some use cases:
/**
* Simple promise factory
*/
angular.module('app').factory('P', function($q) {
var P = $q;
// Make a promise
P.then = function(obj) {
return $q.when(obj);
};
// Take a promise. Queue 'action'. On 'action' faulure, run 'error' and continue.
P.queue = function(promise, action, error) {
return promise.then(action).catch(error);
};
// Oook! Monkey patch .queue() onto a $q promise.
P.startQueue = function(obj) {
var promise = $q.when(obj);
promise.queue = function(action, error) {
return promise.then(action).catch(error);
};
return promise;
};
return P;
});
How to use it:
.run(function($state, YouReallyNeedJustQorP, $q, P) {
// Use a $q promise. Queue actions with P
// Make a regular old promise
var myPromise = $q.when('plain old promise');
// use P to queue an action on myPromise
P.queue(myPromise, function() { return console.log('myPromise: do something clever'); });
// use P to queue an action
P.queue(myPromise, function() {
throw console.log('myPromise: do something dangerous');
}, function() {
return console.log('myPromise: risks must be taken!');
});
// use P to queue an action
P.queue(myPromise, function() { return console.log('myPromise: never quit'); });
// Same thing, but make a special promise with P
var myQueue = P.startQueue(myPromise);
// use P to queue an action
myQueue.queue(function() { return console.log('myQueue: do something clever'); });
// use P to queue an action
myQueue.queue(function() {
throw console.log('myQueue: do something hard');
}, function() {
return console.log('myQueue: hard is interesting!');
});
// use P to queue an action
myQueue.queue(function() { return console.log('myQueue: no easy days'); });
Chained Promises
Angular's $q implementation allows you to chain promises, and then handle resolves of those promises according to your own logic. The methods are a bit different than mbostock/queue, but the intent is the same. Create a function that determines how your defered will be resolved (creating a promise), then make these available to a higher level controller/service for specific resolution handling.
Angular uses $q.defer() to return promise objects, which can then be called in the order you wish inside your application logic. (or even skipped, mutated, intercepted, etc...).
I'll throw down some code, but I found this 7 minute video at egghead.io to be the best short demo: https://egghead.io/lessons/angularjs-chained-promises, and it will do a FAR better job of explaining. Thomas (the presenter) builds a very small flight dashboard app that queues up weather and flight data, and processes that queue when a user queries their itenerary. ThomasBurleson/angularjs-FlightDashboard
I will be setting up a smaller demonstration on codepen, using the situation of 'eating at a restaurant' to demonstrate this concept: http://codepen.io/LongLiveCHIEF/pen/uLyHx
Code examples here:
https://gist.github.com/LongLiveCHIEF/4c5432d1c2fb2fdf937d

how to make $.when execute when one of JSON request fail

I'm using the below code to get JSON from multiple urls. However, when one of the URL failed or get 404 response the function the execute doesn't work. I read the jquery doc and I know "then" should execute no matter one of the call has failed.
var data = {};
var calls = [];
for (var i in funcs) {
calls.push(
$.getJSON(base_url+i,
(function(i) {
return function(d) {
data[i] = d;
};
}(i))
)
);
}
$.when.apply($,calls).then(function() {
do_something(data);
});
Take a look at always method. It will executed in both cases.
For example:
$.when.apply($, calls).always(function() {
alert('Resolved or rejected');
});
In response to successful transaction, arguments are same as .done() (ie. a = data, b = jqXHR) and for failed transactions the arguments are same as .fail() (ie. a = jqXHR, b = errorThrown). (c)
I read the jquery doc and I know "then" should execute no matter one of the call has failed.
Nope, the promise only gets fulfilled if all of the passed objects are fulfilled. If one of them fails, the result will get rejected.
is there a way to make do_something(data); execute no matter if it failed or not.
You could use .always:
// doSomething waits until all are fulfilled or one is rejected
$.when.apply($,calls).always(do_something);
Yet, you probably want to execute the callback when all calls are resolved (no matter if fulfilled or rejected) - like allSettled does in Q. With jQuery, you have to work around a little:
var calls = $.map(funcs, function(_, i) {
var d = new $.Deferred;
$.getJSON(base_url+i).done(function(r/*…*/) {
d.resolve(i, r);
}, function(/*…*/) {
d.resolve();
});
return d.promise();
});
$.when.apply($, calls).then(function() {
var data = {};
for (var i=0; i<arguments.length; i++)
data[arguments[i][0]] = arguments[i][1];
do_something(data);
});
As jQuery docs the .then function takes two (or three) arguments in your case deferred.then( doneCallbacks, failCallbacks )
So you need to specify the second function to handle the failing request.
i.e.
$.when.apply($,calls).then(function() {
alerT('ok');
}, function() {
alert('fail');
});
Here a simple fiddle: http://jsfiddle.net/rrMwr/
Hope this helps
You need to use the second parameter of deferred.then() (documentation):
$.when.apply($, calls).then(function() {
do_something(data);
}, function() {
// something failed
});
If you want to call the same callback regardless of successes and failures, use deferred.always() (documentation):
$.when.apply($, calls).always(function() {
do_something(data);
});
It is also worth reading the jQuery.when() documentation, which explains the aggregation when multiple deferred objects are passed.

How can jQuery deferred be used?

jQuery 1.5 brings the new Deferred object and the attached methods .when, .Deferred and ._Deferred.
For those who haven't used .Deferred before, I've annotated the source for it.
What are the possible usages of these new methods, how do we go about fitting them into patterns?
I have already read the API and the source, so I know what it does. My question is how can we use these new features in everyday code?
I have a simple example of a buffer class that calls AJAX requests in order. (Next one starts after the previous one finishes).
/* Class: Buffer
* methods: append
*
* Constructor: takes a function which will be the task handler to be called
*
* .append appends a task to the buffer. Buffer will only call a task when the
* previous task has finished
*/
var Buffer = function(handler) {
var tasks = [];
// empty resolved deferred object
var deferred = $.when();
// handle the next object
function handleNextTask() {
// if the current deferred task has resolved and there are more tasks
if (deferred.isResolved() && tasks.length > 0) {
// grab a task
var task = tasks.shift();
// set the deferred to be deferred returned from the handler
deferred = handler(task);
// if its not a deferred object then set it to be an empty deferred object
if (!(deferred && deferred.promise)) {
deferred = $.when();
}
// if we have tasks left then handle the next one when the current one
// is done.
if (tasks.length > 0) {
deferred.done(handleNextTask);
}
}
}
// appends a task.
this.append = function(task) {
// add to the array
tasks.push(task);
// handle the next task
handleNextTask();
};
};
I'm looking for demonstrations and possible uses of .Deferred and .when.
It would also be lovely to see examples of ._Deferred.
Linking to the new jQuery.ajax source for examples is cheating.
I am particularly interested in what techniques are available when we abstract away whether an operation is synchronously or asynchronously done.
The best use case I can think of is in caching AJAX responses. Here's a modified example from Rebecca Murphey's intro post on the topic:
var cache = {};
function getData( val ){
// return either the cached value or jqXHR object wrapped Promise
return $.when(
cache[ val ] ||
$.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function( resp ){
cache[ val ] = resp;
}
})
);
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retrieved using an
// XHR request.
});
Basically, if the value has already been requested once before it's returned immediately from the cache. Otherwise, an AJAX request fetches the data and adds it to the cache. The $.when/.then doesn't care about any of this; all you need to be concerned about is using the response, which is passed to the .then() handler in both cases. jQuery.when() handles a non-Promise/Deferred as a Completed one, immediately executing any .done() or .then() on the chain.
Deferreds are perfect for when the task may or may not operate asynchronously, and you want to abstract that condition out of the code.
Another real world example using the $.when helper:
$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {
$(tmpl) // create a jQuery object out of the template
.tmpl(data) // compile it
.appendTo("#target"); // insert it into the DOM
});
Here is a slightly different implementation of an AJAX cache as in ehynd's answer.
As noted in fortuneRice's follow-up question, ehynd's implementation didn't actually prevent multiple identical requests if the requests were performed before one of them had returned. That is,
for (var i=0; i<3; i++) {
getData("xxx");
}
will most likely result in 3 AJAX requests if the result for "xxx" has not already been cached before.
This can be solved by caching the request's Deferreds instead of the result:
var cache = {};
function getData( val ){
// Return a promise from the cache (if available)
// or create a new one (a jqXHR object) and store it in the cache.
var promise = cache[val];
if (!promise) {
promise = $.ajax('/foo/', {
data: { value: val },
dataType: 'json'
});
cache[val] = promise;
}
return promise;
}
$.when(getData('foo')).then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
A deferred can be used in place of a mutex. This is essentially the same as the multiple ajax usage scenarios.
MUTEX
var mutex = 2;
setTimeout(function() {
callback();
}, 800);
setTimeout(function() {
callback();
}, 500);
function callback() {
if (--mutex === 0) {
//run code
}
}
DEFERRED
function timeout(x) {
var dfd = jQuery.Deferred();
setTimeout(function() {
dfd.resolve();
}, x);
return dfd.promise();
}
jQuery.when(
timeout(800), timeout(500)).done(function() {
// run code
});
When using a Deferred as a mutex only, watch out for performance impacts (http://jsperf.com/deferred-vs-mutex/2). Though the convenience, as well as additional benefits supplied by a Deferred is well worth it, and in actual (user driven event based) usage the performance impact should not be noticeable.
This is a self-promotional answer, but I spent a few months researching this and presented the results at jQuery Conference San Francisco 2012.
Here is a free video of the talk:
https://www.youtube.com/watch?v=juRtEEsHI9E
Another use that I've been putting to good purpose is fetching data from multiple sources. In the example below, I'm fetching multiple, independent JSON schema objects used in an existing application for validation between a client and a REST server. In this case, I don't want the browser-side application to start loading data before it has all the schemas loaded. $.when.apply().then() is perfect for this. Thank to Raynos for pointers on using then(fn1, fn2) to monitor for error conditions.
fetch_sources = function (schema_urls) {
var fetch_one = function (url) {
return $.ajax({
url: url,
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
});
}
return $.map(schema_urls, fetch_one);
}
var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(
function () {
var schemas = $.map(arguments, function (a) {
return a[0]
});
start_application(schemas);
}, function () {
console.log("FAIL", this, arguments);
});
Another example using Deferreds to implement a cache for any kind of computation (typically some performance-intensive or long-running tasks):
var ResultsCache = function(computationFunction, cacheKeyGenerator) {
this._cache = {};
this._computationFunction = computationFunction;
if (cacheKeyGenerator)
this._cacheKeyGenerator = cacheKeyGenerator;
};
ResultsCache.prototype.compute = function() {
// try to retrieve computation from cache
var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
var promise = this._cache[cacheKey];
// if not yet cached: start computation and store promise in cache
if (!promise) {
var deferred = $.Deferred();
promise = deferred.promise();
this._cache[cacheKey] = promise;
// perform the computation
var args = Array.prototype.slice.call(arguments);
args.push(deferred.resolve);
this._computationFunction.apply(null, args);
}
return promise;
};
// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
return Array.prototype.slice.call(arguments).join("|");
};
Here is an example of using this class to perform some (simulated heavy) calculation:
// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
console.log("Performing computation: adding " + a + " and " + b);
// simulate rather long calculation time by using a 1s timeout
setTimeout(function() {
var result = a + b;
resultHandler(result);
}, 1000);
});
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
addingMachine.compute(1, 1).then(function(result) {
console.log("result: " + result);
});
// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
console.log("result: " + result);
});
The same underlying cache could be used to cache Ajax requests:
var ajaxCache = new ResultsCache(function(id, resultHandler) {
console.log("Performing Ajax request for id '" + id + "'");
$.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
resultHandler(data.value);
});
});
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
ajaxCache.compute("anotherID").then(function(result) {
console.log("result: " + result);
});
// cached result will be used
ajaxCache.compute("anID").then(function(result) {
console.log("result: " + result);
});
You can play with the above code in this jsFiddle.
1) Use it to ensure an ordered execution of callbacks:
var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });
step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.
step2.resolve();
step3.resolve();
step1.resolve();
2) Use it to verify the status of the app:
var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred
jQuery.when(loggedIn, databaseReady).then(function() {
//do something
});
You can use a deferred object to make a fluid design that works well in webkit browsers. Webkit browsers will fire resize event for each pixel the window is resized, unlike FF and IE which fire the event only once for each resize. As a result, you have no control over the order in which the functions bound to your window resize event will execute. Something like this solves the problem:
var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();
function resizeAlgorithm() {
//some resize code here
}
$(window).resize(function() {
resizeQueue.done(resizeAlgorithm);
});
This will serialize the execution of your code so that it executes as you intended it to. Beware of pitfalls when passing object methods as callbacks to a deferred. Once such method is executed as a callback to deferred, the 'this' reference will be overwritten with reference to the deferred object and will no longer refer to the object the method belongs to.
You can also integrate it with any 3rd-party libraries which makes use of JQuery.
One such library is Backbone, which is actually going to support Deferred in their next version.
I've just used Deferred in real code. In project jQuery Terminal I have function exec that call commands defined by user (like he was entering it and pressing enter), I've added Deferreds to the API and call exec with arrays. like this:
terminal.exec('command').then(function() {
terminal.echo('command finished');
});
or
terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
terminal.echo('all commands finished');
});
the commands can run async code, and exec need to call user code in order. My first api use pair of pause/resume calls and in new API I call those automatic when user return promise. So user code can just use
return $.get('/some/url');
or
var d = new $.Deferred();
setTimeout(function() {
d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();
I use code like this:
exec: function(command, silent, deferred) {
var d;
if ($.isArray(command)) {
return $.when.apply($, $.map(command, function(command) {
return self.exec(command, silent);
}));
}
// both commands executed here (resume will call Term::exec)
if (paused) {
// delay command multiple time
d = deferred || new $.Deferred();
dalyed_commands.push([command, silent, d]);
return d.promise();
} else {
// commands may return promise from user code
// it will resolve exec promise when user promise
// is resolved
var ret = commands(command, silent, true, deferred);
if (!ret) {
if (deferred) {
deferred.resolve(self);
return deferred.promise();
} else {
d = new $.Deferred();
ret = d.promise();
ret.resolve();
}
}
return ret;
}
},
dalyed_commands is used in resume function that call exec again with all dalyed_commands.
and part of the commands function (I've stripped not related parts)
function commands(command, silent, exec, deferred) {
var position = lines.length-1;
// Call user interpreter function
var result = interpreter.interpreter(command, self);
// user code can return a promise
if (result != undefined) {
// new API - auto pause/resume when using promises
self.pause();
return $.when(result).then(function(result) {
// don't echo result if user echo something
if (result && position === lines.length-1) {
display_object(result);
}
// resolve promise from exec. This will fire
// code if used terminal::exec('command').then
if (deferred) {
deferred.resolve();
}
self.resume();
});
}
// this is old API
// if command call pause - wait until resume
if (paused) {
self.bind('resume.command', function() {
// exec with resume/pause in user code
if (deferred) {
deferred.resolve();
}
self.unbind('resume.command');
});
} else {
// this should not happen
if (deferred) {
deferred.resolve();
}
}
}
The answer by ehynds will not work, because it caches the responses data. It should cache the jqXHR which is also a Promise.
Here is the correct code:
var cache = {};
function getData( val ){
// return either the cached value or an
// jqXHR object (which contains a promise)
return cache[ val ] || $.ajax('/foo/', {
data: { value: val },
dataType: 'json',
success: function(data, textStatus, jqXHR){
cache[ val ] = jqXHR;
}
});
}
getData('foo').then(function(resp){
// do something with the response, which may
// or may not have been retreived using an
// XHR request.
});
The answer by Julian D. will work correct and is a better solution.

Categories