Multiple jQuery Ajax requests and response handling [duplicate] - javascript

Here's an contrived example of what's going on: http://jsfiddle.net/adamjford/YNGcm/20/
HTML:
Click me!
<div></div>
JavaScript:
function getSomeDeferredStuff() {
var deferreds = [];
var i = 1;
for (i = 1; i <= 10; i++) {
var count = i;
deferreds.push(
$.post('/echo/html/', {
html: "<p>Task #" + count + " complete.",
delay: count
}).success(function(data) {
$("div").append(data);
}));
}
return deferreds;
}
$(function() {
$("a").click(function() {
var deferreds = getSomeDeferredStuff();
$.when(deferreds).done(function() {
$("div").append("<p>All done!</p>");
});
});
});
I want "All done!" to appear after all of the deferred tasks have completed, but $.when() doesn't appear to know how to handle an array of Deferred objects. "All done!" is happening first because the array is not a Deferred object, so jQuery goes ahead and assumes it's just done.
I know one could pass the objects into the function like $.when(deferred1, deferred2, ..., deferredX) but it's unknown how many Deferred objects there will be at execution in the actual problem I'm trying to solve.

To pass an array of values to any function that normally expects them to be separate parameters, use Function.prototype.apply, so in this case you need:
$.when.apply($, my_array).then( ___ );
See http://jsfiddle.net/YNGcm/21/
In ES6, you can use the ... spread operator instead:
$.when(...my_array).then( ___ );
In either case, since it's unlikely that you'll known in advance how many formal parameters the .then handler will require, that handler would need to process the arguments array in order to retrieve the result of each promise.

The workarounds above (thanks!) don't properly address the problem of getting back the objects provided to the deferred's resolve() method because jQuery calls the done() and fail() callbacks with individual parameters, not an array. That means we have to use the arguments pseudo-array to get all the resolved/rejected objects returned by the array of deferreds, which is ugly:
$.when.apply($,deferreds).then(function() {
var objects = arguments; // The array of resolved objects as a pseudo-array
...
};
Since we passed in an array of deferreds, it would be nice to get back an array of results. It would also be nice to get back an actual array instead of a pseudo-array so we can use methods like Array.sort().
Here is a solution inspired by when.js's when.all() method that addresses these problems:
// Put somewhere in your scripting environment
if (typeof jQuery.when.all === 'undefined') {
jQuery.when.all = function (deferreds) {
return $.Deferred(function (def) {
$.when.apply(jQuery, deferreds).then(
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that succeeded. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.done:
// ( data, textStatus, jqXHR )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.resolveWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
},
// the calling function will receive an array of length N, where N is the number of
// deferred objects passed to when.all that failed. each element in that array will
// itself be an array of 3 objects, corresponding to the arguments passed to jqXHR.fail:
// ( jqXHR, textStatus, errorThrown )
function () {
var arrayThis, arrayArguments;
if (Array.isArray(this)) {
arrayThis = this;
arrayArguments = arguments;
}
else {
arrayThis = [this];
arrayArguments = [arguments];
}
def.rejectWith(arrayThis, [Array.prototype.slice.call(arrayArguments)]);
});
});
}
}
Now you can simply pass in an array of deferreds/promises and get back an array of resolved/rejected objects in your callback, like so:
$.when.all(deferreds).then(function(objects) {
console.log("Resolved objects:", objects);
});

You can apply the when method to your array:
var arr = [ /* Deferred objects */ ];
$.when.apply($, arr);
How do you work with an array of jQuery Deferreds?

When calling multiple parallel AJAX calls, you have two options for handling the respective responses.
Use Synchronous AJAX call/ one after another/ not recommended
Use Promises' array and $.when which accepts promises and its callback .done gets called when all the promises are return successfully with respective responses.
Example
function ajaxRequest(capitalCity) {
return $.ajax({
url: 'https://restcountries.eu/rest/v1/capital/'+capitalCity,
success: function(response) {
},
error: function(response) {
console.log("Error")
}
});
}
$(function(){
var capitalCities = ['Delhi', 'Beijing', 'Washington', 'Tokyo', 'London'];
$('#capitals').text(capitalCities);
function getCountryCapitals(){ //do multiple parallel ajax requests
var promises = [];
for(var i=0,l=capitalCities.length; i<l; i++){
var promise = ajaxRequest(capitalCities[i]);
promises.push(promise);
}
$.when.apply($, promises)
.done(fillCountryCapitals);
}
function fillCountryCapitals(){
var countries = [];
var responses = arguments;
for(i in responses){
console.dir(responses[i]);
countries.push(responses[i][0][0].nativeName)
}
$('#countries').text(countries);
}
getCountryCapitals()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<h4>Capital Cities : </h4> <span id="capitals"></span>
<h4>Respective Country's Native Names : </h4> <span id="countries"></span>
</div>

As a simple alternative, that does not require $.when.apply or an array, you can use the following pattern to generate a single promise for multiple parallel promises:
promise = $.when(promise, anotherPromise);
e.g.
function GetSomeDeferredStuff() {
// Start with an empty resolved promise (or undefined does the same!)
var promise;
var i = 1;
for (i = 1; i <= 5; i++) {
var count = i;
promise = $.when(promise,
$.ajax({
type: "POST",
url: '/echo/html/',
data: {
html: "<p>Task #" + count + " complete.",
delay: count / 2
},
success: function (data) {
$("div").append(data);
}
}));
}
return promise;
}
$(function () {
$("a").click(function () {
var promise = GetSomeDeferredStuff();
promise.then(function () {
$("div").append("<p>All done!</p>");
});
});
});
Notes:
I figured this one out after seeing someone chain promises sequentially, using promise = promise.then(newpromise)
The downside is it creates extra promise objects behind the scenes and any parameters passed at the end are not very useful (as they are nested inside additional objects). For what you want though it is short and simple.
The upside is it requires no array or array management.

I want to propose other one with using $.each:
We may to declare ajax function like:
function ajaxFn(someData) {
this.someData = someData;
var that = this;
return function () {
var promise = $.Deferred();
$.ajax({
method: "POST",
url: "url",
data: that.someData,
success: function(data) {
promise.resolve(data);
},
error: function(data) {
promise.reject(data);
}
})
return promise;
}
}
Part of code where we creating array of functions with ajax to send:
var arrayOfFn = [];
for (var i = 0; i < someDataArray.length; i++) {
var ajaxFnForArray = new ajaxFn(someDataArray[i]);
arrayOfFn.push(ajaxFnForArray);
}
And calling functions with sending ajax:
$.when(
$.each(arrayOfFn, function(index, value) {
value.call()
})
).then(function() {
alert("Cheer!");
}
)

If you're transpiling and have access to ES6, you can use spread syntax which specifically applies each iterable item of an object as a discrete argument, just the way $.when() needs it.
$.when(...deferreds).done(() => {
// do stuff
});
MDN Link - Spread Syntax

I had a case very similar where I was posting in an each loop and then setting the html markup in some fields from numbers received from the ajax. I then needed to do a sum of the (now-updated) values of these fields and place in a total field.
Thus the problem was that I was trying to do a sum on all of the numbers but no data had arrived back yet from the async ajax calls. I needed to complete this functionality in a few functions to be able to reuse the code. My outer function awaits the data before I then go and do some stuff with the fully updated DOM.
// 1st
function Outer() {
var deferreds = GetAllData();
$.when.apply($, deferreds).done(function () {
// now you can do whatever you want with the updated page
});
}
// 2nd
function GetAllData() {
var deferreds = [];
$('.calculatedField').each(function (data) {
deferreds.push(GetIndividualData($(this)));
});
return deferreds;
}
// 3rd
function GetIndividualData(item) {
var def = new $.Deferred();
$.post('#Url.Action("GetData")', function (data) {
item.html(data.valueFromAjax);
def.resolve(data);
});
return def;
}

If you're using angularJS or some variant of the Q promise library, then you have a .all() method that solves this exact problem.
var savePromises = [];
angular.forEach(models, function(model){
savePromises.push(
model.saveToServer()
)
});
$q.all(savePromises).then(
function success(results){...},
function failed(results){...}
);
see the full API:
https://github.com/kriskowal/q/wiki/API-Reference#promiseall
https://docs.angularjs.org/api/ng/service/$q

Related

Need to make multiple Async calls before executing next step JS

I have an array that can hold an unknown amount of indexes in it. Each index is used to send data with an ajax call. I am looping through with a for loop gathering the data from the successful call and pushing it into an empty array. At the end of the unknown amount of calls I then need to use that newly gathered array in my view. newDataArray is executed at the bottom before the loops are done and therefor it is still empty. How do I finish all the calls then do what is at the bottom?
If it helps, I am doing this in React with the Flux pattern. But the same issue may be done not in React. Here is a mock sample of what I am trying to do:
JS
case 'execute-calls':
//This is the new array to push to
var newDataArray = [];
//Url to call
var url = 'http://dev.markitondemand.com/Api/v2/Quote/jsonp';
for(let i = 0; i < payload.data.length; i++){
//given array of data that needs to be sent with call
let symb = { symbol: payload.data[i]};
$.ajax({
data: symb,
url: url,
dataType: "jsonp",
})
.done(function(data){
let updatedData = {
//...data that is stored from response
};
newDataArray.push(updatedData);
})
.fail(function(error){
//console.log(error);
});
}
//This will be updating the state object which is above the switch cases
//However this is ran before the end of the loops so newDataArray is empty
var updateTicker = {
updatedTicker: true,
updatedTickerSymbols: newDataArray
};
assign(stockData,updateTicker);
getStockData.emitChange();
break;
You can make use of the fact that $.ajax() actually returns a deferred object, and use it to create an array of deferreds. e.g.
var symbols = [1, 2, 3, 4];
var deferreds = symbols.map(function (symbol) {
return $.ajax({
url: 'http://dev.markitondemand.com/MODApis/Api/v2/Quote/jsonp',
data: { symbol: symbol },
dataType: 'jsonp'
});
});
You can resolve multiple deferreds at once with $.when(). There is a complication however, $.when() expects a list of parameters rather than array. We can solve this by using Function#apply.
To add to the complication, the callback function is also called with a list of arguments. Since we don't know how many arguments there are, we'll use the arguments pseudo-array. And since arguments isn't an actual array, we'll loop through it by using Function#call on Array#prototype.
$.when.apply($, deferreds).done(function () {
Array.prototype.forEach.call(arguments, function (response) {
console.log(response[0].Message);
});
}).fail(function (jqXHR, textStatus, error) {
console.error(error);
});
[UPDATED to include fail() call]
If you're using ES6 this is much more elegant:
$.when(...deferreds).done((...responses) => {
responses.forEach((response) => {
console.log(response[0].Message);
});
});
When ever you are dealing with ajax calls and have to do some operations at the end of all async calls then better choice would be to use Callback functions.
Modifying your code to use the call back,
function AsyncLoopHandler(index) {
if (index > payload.data.length) {
// all the indexes have finished ajax calls do your next step here
var updateTicker = {
updatedTicker: true,
updatedTickerSymbols: newDataArray
};
assign(stockData, updateTicker);
getStockData.emitChange();
}
else {
//given array of data that needs to be sent with call
let symb = { symbol: payload.data[index] };
$.ajax({
data: symb,
url: url,
dataType: "jsonp",
})
.done(function (data) {
let updatedData = {
//...data that is stored from response
};
newDataArray.push(updatedData);
AsyncLoopHandler(index++); // call the function again with new index
})
.fail(function (error) {
//console.log(error);
});
}
}
Now for starting this recursive function just start it by passing the index 0.
AsyncLoopHandler(0);
So all the ajax calls will be executed one after the other as if its an synchronous requests, And the if check will see if all the indexes are complete and then run your logic. Let me know if this helps
suggest use promise, logic would like
var urls= [x,x,x,x];
var results = [];
var qs = $.map(urls,function(url){
return function(){
var deferred = Q.defer();
$.ajax({
success:function(){
results.push(url)
deferred.reslove();
},error:function(){
deferred.reslove();
}
})
return deferred;
}
})
Q.all(qs).then(function(){
console.log(results )
});
or use yield and co in new standard
https://github.com/kriskowal/q

Multiple jQuery promises in sequential order

Basically I want this:
function do_ajax_calls(...){
var d = $.Deferred();
$.ajax(args).done(function(){
$.ajax(args).done(function(){
$.ajax(args).done(function(){
d.resolve();
});
});
})
return d.promise();
}
But the number of ajax calls depends on the arguments that I pass to the function, which is an array, so I can't use that code.
The function should return a promise that only resolves when the last ajax calls completes. So the function needs to be called like this:
do_ajax_calls(....).done(function(){
// here is the callback
})
Does anyone know how can I do this?
But the number of ajax calls depends on the arguments that I pass to the function, which is an array
If it's one ajax call per array item
function do_ajax_calls(args) {
return args.reduce(function(promise, item) {
return promise.then(function() {
return $.ajax(args); // should that be item?
});
}, Promise.resolve(true));
}
The Promise.resolve(true) is a "native" promise, i.e. not available in IE, but I'm sure jQuery has an equivalent
Here's a JSFiddle Demo
One of the reasons promises are a big deal is because they can be chained. You can use this to your advantage to iteratively chain additional requests onto the resolution of the previous one:
function do_ajax_calls() {
var dfd = $.Deferred();
var promise = dfd.promise();
var responses = [];
function chainRequest(url) {
promise = promise.then(function (response) {
responses.push(response);
return $.ajax(url, { method: 'POST' });
});
}
for (var i = 0, length = arguments.length; i < length; i++) {
chainRequest(arguments[i]);
}
dfd.resolve();
return promise.then(function (response) {
return responses.slice(1).concat(response);
});
}
The above code will return a promise ultimately resolving to an array of all of the responses. If any one of the requests fails, the promise will reject with the first failure.
JSFiddle
Here is it Demo
var counter = 1 ;
function multipleAjax(loop)
{
if(counter<loop)
{
$.ajax(
{
url: 'http://mouadhhsoumi.tk/echo.php',
success:function(data)
{
multipleAjax(loop);
$(".yo").append(data+"</br>");
counter++;
}
});
}
}
multipleAjax(5);
Try using $.when() , Function.prototype.apply() , $.map()
function do_ajax_calls(args) {
return $.when.apply($, $.map(args, function(request, i) {
return $.ajax(request) // `request` : item with `args` array
}))
}
do_ajax_calls
.then(function success() {
console.log(arguments)
}, function err() {
console.log("err", arguments)
});

how to resolve all promises in a for loop?

Yet another question about promises. I have this scenario:
Service.prototype.parse = function (data) {
var deferred = $.Deferred();
var arr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
$.when(details).then(function (data) {
arr.push(data);
deferred.resolve(arr);
});
}
return deferred.promise;
};
Somewhere else in the code:
...
$.when(parse()).then(function (resp) {
//...
});
The promises get resolved at some point but initially resp has a length of 1.
How to wait for parse() to resolve everything and return a array?
No need for a deferred anti pattern (explicit construction) or for explicit array construction. Your code can be simplified to:
Service.prototype.parse = function (data) {
return $.when.apply($, data.map(function(x){
return new Details();
}).then(function(){ return arguments; });//.then(Array.of); // instead, if want array
};
Some general advice:
You're ignoring the item you're iterating in the data, I assume you don't do that in your real code but just making sure.
It's generally not the best idea to perform async IO in a constructor. Please consider separating construction and initialization.
You should consider using native promises (or a library) or using jQuery 3.0.0 which supports non problematic promises.
Please try this:
Service.prototype.parse = function(data) {
var detailsArr = [];
for (var i = 0; i < data.length; i++) {
var details = new Details();
detailsArr.push(details);
}
return $.when.apply($, detailsArr).then(function(){
return Array.prototype.slice.call(arguments);
});
};
Here we put all Details into an array, use $.when.apply($, arr) to wait for all Details get resolved. When it's done, each Details' return data will be passed to callback function as one parameter, so the call back function will receive total data.length number of parameters. Then we use Array.prototype.slice.call to convert all parameters to an array and return the result.
For your reference:
What does $.when.apply($, someArray) do?
How can I convert the "arguments" object to an array in JavaScript?

Passing different arguments to deferred.resolve() for different handlers

I'm trying to sync up multiple ajax callbacks using jQuery.Deferrd objects. Obviously jQuery.when handles this for you however my code is architected in such a way that the ajax requests aren't called in the same method. So for example this is the flow:
// A Button is clicked
// Module 1 requests a snippet of html and updates the DOM
// Module 2 requests a different snippet of html and updates the DOM
I need both Modules to update the DOM at the same time meaning I need to ensure the callbacks are run after both requests have returned.
Module 1 and Module 2 need to be able to exist without each other and should have no knowledge of one another so the requests can't be made together using $.when(doMod1Request(), doMod2Request()).then(function () { ... }) and the callbacks should be independent too.
I've therefore written a wrapper around ajax which adds the callbacks to a deferred object and in a similar way to $.when resolves the deferred object once the ajax requests have returned the same number of times as the number of callbacks on the deferred object.
My dilemma is however deferred.resolve() can only be called with one set of arguments so each callback get's the same value.
e.g.
var deferred = new $.Deferred();
deferred.done(function (response) {
console.log(response); // <div class="html-snippet-1"></div>
});
deferred.done(function (response) {
console.log(response); // <div class="html-snippet-1"></div>
});
deferred.resolve('<div class="html-snippet-1"></div>');
Whereas I'd want something like this:
var deferred = new $.Deferred();
deferred.done(function (response) {
console.log(response); // <div class="html-snippet-1"></div>
});
deferred.done(function (response) {
console.log(response); // <div class="html-snippet-2"></div>
});
deferred.resolve(['<div class="html-snippet-1"></div>', '<div class="html-snippet-2"></div>']);
Is this possible or am I going about this incorrectly?
I'd say this is perfectly valid. Assuming your independent modules, you would do (with two Promises):
doMod1Request().done(doMod1Update);
doMod2Request().done(doMod2Update);
Now, if you want to to execute the updates together and only if the two requests both succeeded, just write
$.when(doMod1Request(), doMod2Request()).done(function(mod1result, mod2result) {
doMod1Update(mod1result);
doMod2Update(mod2result);
});
This only gets ugly if you call your resolve functions with multiple arguments, as jQuery is a bit inconsistent there and does not really distinguish multiple arguments from one array argument.
To uncouple them with that publish-subscribe pattern you are using, I'd recommend the following:
function Combination() {
this.deferreds = [];
this.success = [];
this.error = [];
}
Combination.prototype.add = function(def, suc, err) {
this.deffereds.push(def);
this.success.push(suc);
this.error.push(err);
};
Combination.prototype.start = function() {
var that = this;
return $.when.apply($, this.deferreds).always(function() {
for (var i=0; i<that.deferreds.length; i++)
that.deferreds[i].done(that.success[i]).fail(that.error[i]);
// of course we could also call them directly with the arguments[i]
});
};
// Then do
var comb = new Combination();
window.notifyModules("something happened", comb); // get deferreds and handlers
comb.start();
// and in each module
window.listen("something happended", function(c) {
c.add(doRequest(), doUpdate, doErrorHandling);
});
Let's assume your modules look something like this :
var MODULE_1 = function() {
function getSnippet() {
return $.ajax({
//ajax options here
});
}
return {
getSnippet: getSnippet
}
}();
var MODULE_2 = function() {
function getSnippet() {
return $.ajax({
//ajax options here
});
}
return {
getSnippet: getSnippet
}
}();
Don't worry if your modules are different, the important thing is that the getSnippet functions each return a jqXHR object, which (as of jQuery 1.5) implements the Promise interface.
Now, let's assume you want to fetch the two snippets in response to some event (say a button click) and do something when both ajax responses have been received, then the click handler will be something like this:
$("myButton").on('click', function(){
var snippets = [];
var promises_1 = MODULE_1.getSnippet().done(function(response){
snippets.push({
target: $("#div_1"),
response: response
});
});
var promise_2 = MODULE_2.getSnippet().done(function(response){
snippets.push({
target: $("#div_2"),
response: response
});
});
$.when(promise_1, promise_2).done(function() {
$.each(snippets, function(i, snippetObj) {
snippetObj.target.html(snippetObj.response);
});
});
});
Slightly more elaborate, and better if you have many similarly constructed modules to fetch many snippets, would be something like this:
$(function(){
$("myButton").on('click', function(){
var promises = [];
var snippets = [];
var modules = [MODULE_1, MODULE_2, MODULE_3 .....];
for (var i=1; i<=10; i++) {
promises.push(modules[i].getSnippet().done(function(response){
snippets.push({
target: $("#div_" + i),
response: response
};
}));
}
$.when.apply(this, promises).done(function() {
$.each(snippets, function(i, snippetObj) {
snippetObj.target.html(snippetObj.response);
});
});
});
});
As you can see, I've made heaps of assumptions here, but you should get some idea of how to proceed.
To ensure each callback is passed the appropriate arguments I've done the following:
var guid = 0,
deferreds = [];
window.request = function (url, deferred, success) {
var requestId = guid++;
if ($.inArray(deferred) === -1) {
deferreds.push(deferred);
$.extend(deferred, {
requestCount: 0,
responseCount: 0,
args: {}
});
}
deferred.requestCount++;
deferred
.done(function () {
// Corresponding arguments are passed into success callback using requestId
// which is unique to each request.
success.apply(this, deferred.args[requestId]);
});
$.ajax(url, {
success: function () {
// Store arguments on deferrds args obj.
deferred.args[requestId] = arguments;
deferred.responseCount++;
if (deferred.requestCount === deferred.responseCount) {
deferred.resolveWith(this);
}
}
});
};
So the arguments are managed through the closure. This allows me to ensure that both modules have no knowledge of each other and won't break if the other doesn't exist, e.g:
var MODULE_1 = function () {
$(".myButton").on('click', function() {
// Cross module communication is achieved through notifications.
// Pass along a new deferred object with notification for use in window.request
window.notify('my-button-clicked', new $.Deferred);
});
}();
var MODULE_2 = function () {
// run get snippet when 'my-button-clicked' notification is fired
window.listen('my-button-clicked', getSnippet);
function getSnippet (deferred) {
window.request('/module2', deferred, function () {
console.log('module2 success');
});
}
}();
var MODULE_3 = function () {
// run get snippet when 'my-button-clicked' notification is fired
window.listen('my-button-clicked', getSnippet);
function getSnippet (deferred) {
window.request('/module3', deferred, function () {
console.log('module3 success');
});
}
}();
The above allows each module to function independently meaning one will work without the other which loosely couples the code and because both MODULE_2 and MODULE_3 pass the same deferred object into window.request they will be resolved once both requests have successfully returned.
This was my final implementation:
https://github.com/richardscarrott/ply/blob/master/src/ajax.js

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