I'm reading about Deferreds and Promises and keep coming across $.when.apply($, someArray). I'm a little unclear on what this does exactly, looking for an explanation that one line works exactly (not the entire code snippet). Here's some context:
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
$.when.apply($, processItemsDeferred).then(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
return dfd.promise();
}
function everythingDone(){
console.log('processed all items');
}
.apply is used to call a function with an array of arguments. It takes each element in the array, and uses each as a parameter to the function. .apply can also change the context (this) inside a function.
So, let's take $.when. It's used to say "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters.
In your case, you have an array of promises; you don't know how many parameters you're passing to $.when. Passing the array itself to $.when wouldn't work, because it expects its parameters to be promises, not an array.
That's where .apply comes in. It takes the array, and calls $.when with each element as a parameter (and makes sure the this is set to jQuery/$), so then it all works :-)
$.when takes any number of parameters and resolves when all of these have resolved.
anyFunction.apply(thisValue, arrayParameters) calls the function anyFunction setting its context (thisValue will be the this within that function call) and passes all the objects in arrayParameters as individual parameters.
For example:
$.when.apply($, [def1, def2])
Is the same as:
$.when(def1, def2)
But the apply way of calling allows you to pass an array of unknown number of parameters. (In your code, you are saying that you data comes from a service, then that is the only way to call $.when)
Here, the code fully documented.
// 1. Declare an array of 4 elements
var data = [1,2,3,4]; // the ids coming back from serviceA
// 2. Declare an array of Deferred objects
var processItemsDeferred = [];
// 3. For each element of data, create a Deferred push push it to the array
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
// 4. WHEN ALL Deferred objects in the array are resolved THEN call the function
// Note : same as $.when(processItemsDeferred[0], processItemsDeferred[1], ...).then(everythingDone);
$.when.apply($, processItemsDeferred).then(everythingDone);
// 3.1. Function called by the loop to create a Deferred object (data is numeric)
function processItem(data) {
// 3.1.1. Create the Deferred object and output some debug
var dfd = $.Deferred();
console.log('called processItem');
// 3.1.2. After some timeout, resolve the current Deferred
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
// 3.1.3. Return that Deferred (to be inserted into the array)
return dfd.promise();
}
// 4.1. Function called when all deferred are resolved
function everythingDone(){
// 4.1.1. Do some debug trace
console.log('processed all items');
}
Unfortunately I can not agree with you guys.
$.when.apply($, processItemsDeferred).always(everythingDone);
Will call everythingDone as soon as one deferred gets rejected, even if there are other deferreds that are pending.
Heres the full script (I recommend http://jsfiddle.net/):
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
processItemsDeferred.push($.Deferred().reject());
//processItemsDeferred.push($.Deferred().resolve());
$.when.apply($, processItemsDeferred).always(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve(); }, 2000);
return dfd.promise();
}
function everythingDone(){
alert('processed all items');
}
It this a bug? I would like to use this like the gentleman above described it.
Maybe someone can find this useful:
$.when.apply($, processItemsDeferred).then(everythingDone).fail(noGood);
everythingDone isn't called in case of any reject
$.when alone makes it possible for a callback to be called when every promises passed to it are resolved/rejected. Normally, $.when takes a variable number of arguments, using .apply makes it possible to pass it an array of arguments, it's very powerful. For more info on .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
Thanks for your elegant solution:
var promise;
for(var i = 0; i < data.length; i++){
promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);
Just one point: When using resolveWith to get some parameters, it breaks because of the initial promise set to undefined. What i did to make it work:
// Start with an empty resolved promise - undefined does the same thing!
var promise;
for(var i = 0; i < data.length; i++){
if(i==0) promise = processItem(data[i]);
else promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);
Related
I am kind of new to promises and are stuck on the following exercise.
I have an array of values and I want to execute an async call on each one.
In the callback, I want to execute another call on the outcome of the first call.
Basically, my frustration is in the following:
The order of execution should be '1x2x3x' but the order is '123xxx'
In other words, the loop is already going to the next iteration when the sub/nested promise of the first promise is not fullfilled yet..
var values = ["1", "2", "3"];
function do(val) {
var deferred = Q.defer();
asyncCall(val)
.then( function( response ) {
console.log(val);
asyncCall(response)
.then( function ( response ) {
console.log('x');
deferred.resolve(true)
});
});
return deferred.promise;
}
var result = do(values[0]);
values.forEach( function(f) {
result = result.then(do(f));
}
There is probably an easy solution but I'm stuck on it.
You don't need the deferred, that's the deferred anti pattern you have there since promises chain.
Also, you have to return a promise from a .then handler if you want it to wait for it to resolve.
You can simply use a for loop:
function do(val) {
var q = Q();
for(var i = 0; i < val; i++){
q = q.then(asyncCall.bind(null,i))
.then(console.log.bind(console))
.then(console.log.bind(console,"x"));
}
return q; // in case you want to chain
}
fiddle.
Note: Bind just fixates the value for the function call. In this case since the first param (the this value) is null it acts like function(fn,arg){ return function(arg){ return fn(arg); }} that is, it translates a function call to a "partial application" - for more info see the MDN docs.
I am kind of new to promises and are stuck on the following exercise.
I have an array of values and I want to execute an async call on each one.
In the callback, I want to execute another call on the outcome of the first call.
Basically, my frustration is in the following:
The order of execution should be '1x2x3x' but the order is '123xxx'
In other words, the loop is already going to the next iteration when the sub/nested promise of the first promise is not fullfilled yet..
var values = ["1", "2", "3"];
function do(val) {
var deferred = Q.defer();
asyncCall(val)
.then( function( response ) {
console.log(val);
asyncCall(response)
.then( function ( response ) {
console.log('x');
deferred.resolve(true)
});
});
return deferred.promise;
}
var result = do(values[0]);
values.forEach( function(f) {
result = result.then(do(f));
}
There is probably an easy solution but I'm stuck on it.
You don't need the deferred, that's the deferred anti pattern you have there since promises chain.
Also, you have to return a promise from a .then handler if you want it to wait for it to resolve.
You can simply use a for loop:
function do(val) {
var q = Q();
for(var i = 0; i < val; i++){
q = q.then(asyncCall.bind(null,i))
.then(console.log.bind(console))
.then(console.log.bind(console,"x"));
}
return q; // in case you want to chain
}
fiddle.
Note: Bind just fixates the value for the function call. In this case since the first param (the this value) is null it acts like function(fn,arg){ return function(arg){ return fn(arg); }} that is, it translates a function call to a "partial application" - for more info see the MDN docs.
I want to return data that comes from a sqlite query in javascript. The problem is as follows:
App.dbInstantion.transaction(function(tx){
tx.executeSql('SELECT * FROM footsteps', [],
function(tx, results) {
for (var i = 0; i < results.rows.length; i++) {
footsteps.push(results.rows.item(i));
}
//WRONG RETURN SCOPE
return footsteps;
}, self.errorCB
);
}, self.errorCB);
//FOOTSTEPS IS NOT FILLED YET SO AN EMPTY ARRAY IS RETURNED
return footsteps;
I tried to use $.Deferred but that did not solve the problem. Does anyone have a suggestion for this?
Greetz,
Well I would say the approach is wrong.
Returning data does not make sense here. Probably what you would like to do is handing over "footsteps" to the outer function. However, by the time the inner function does execute, the outer did already finish. Whatever you want to do with footsteps, you have to do it from the inner function. Even though the outher function did already finish, you still have access to all the variables, that were defined in context of the outer function. Maybe this helps.
Read about callbacks and closure. Content I can recommend is "JavaScript Patterns" from O'Reilly or any content from Douglas Crockford.
You can actually use deferred objects. Create the deferred object and have the interested code listen to it. Then, do the operation. Once the operation finishes, resolve the deferred object, passing it the data. All listeners to that deferred will receive the data during the resolve.
This is how I figured it out:
First the function itself, it accepts a callback which I will pas tot the .then of the Defferred:
functionName: function(callback, param1, param2) {
var self = this;
var data = function getData(){
var dfd = $.Deferred();
App.dbInstantion.transaction(function(tx){
tx.executeSql('SELECT * FROM blabla',
[], dfd.resolve, self.errorCB
);
}, self.errorCB);
return dfd.promise();
}
//return deferred is done(.then) function with the sent callback to this function
return data().then(callback);
},
Then I use it like this:
initialize: function() {
functionName(this.passedCallback);
},
passedCallback: function(tx, results) {
// I got the results of the query here!
window.footsteps = [];
for (var i = 0; i < results.rows.length; i++) {
window.footsteps.push(results.rows.item(i));
}
//trigger backbone custom event to deal with async problems
App.Vent.trigger('retrievingFootsteps:done');
},
I am looking for a way to do a callback after two ajax calls completes:
$.when(
call1(),
call2()
).always(function() {
// Here I want to be sure the two calls are done and to get their responses
);
The catch is that one of the calls might fail. So, in my code the always will invoked without waiting to the other call.
How can I wait for both calls to done (success or failure)?
Here is something that should do the trick:
$.whenAllDone = function() {
var deferreds = [];
var result = $.Deferred();
$.each(arguments, function(i, current) {
var currentDeferred = $.Deferred();
current.then(function() {
currentDeferred.resolve(false, arguments);
}, function() {
currentDeferred.resolve(true, arguments);
});
deferreds.push(currentDeferred);
});
$.when.apply($, deferreds).then(function() {
var failures = [];
var successes = [];
$.each(arguments, function(i, args) {
// If we resolved with `true` as the first parameter
// we have a failure, a success otherwise
var target = args[0] ? failures : successes;
var data = args[1];
// Push either all arguments or the only one
target.push(data.length === 1 ? data[0] : args);
});
if(failures.length) {
return result.reject.apply(result, failures);
}
return result.resolve.apply(result, successes);
});
return result;
}
Check out this Fiddle to see how it works.
Basically it waits for all Deferreds to finish no matter if they fail or not and collects all the results. If we have failures, the returned Deferred will fail with a list of all failures and resolve with all successes otherwise.
It isn't pretty, but you could have a global "completed" variable for each ajax call to set when complete. Each call would also check whether both variables were set, and if so, call your always function.
You can also nest the calls:
$.when(call1()).always(function(){
$.when(call2()).always(function(){
// Here I want to be sure the two calls are done and to get their responses
});
});
But of course the two calls will become synchronous to each other.
Daff's answer is good. There is only one problem. When there is only one deferred, things don't work.
The problem was inside jquery's when method.
jquery.when: function( subordinate /* , ..., subordinateN */ ) { ...
It has a line like:
// If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
And this changes the shape of the arguments, so I had to put it back to the common shape my code expects (i.e. the same shape when multiple deferreds are passed to whenAllDone)
const jqueryWhenUsesSubordinate = deferreds.length == 1;
const deferredArgs = jqueryWhenUsesSubordinate
? [[ arguments[ 0 ], arguments[ 1 ] ]]
: arguments
$.each(deferredArgs, function (i, resolvedArgs) {
var target = !resolvedArgs[0] ? failures : successes;
var data = resolvedArgs[1];
target.push(data.length === 1 ? data[0] : data);
});
Additionally, I changed the function signature to match more closely to Promise.allSettled in that it should take an array parameter of deferred objects, then instead of looping over arguments to set up the deferreds array, you loop over that parameter passed in.
This allows you to programmatically create a variable length of deferreds into an array and pass that into whenAllDone.
I am calling a method that has some logic in a deferred, and when that logic finishes, I want to return the value to the callee. See below:
//Callee.js
var myAssistant = new Assistant();
console.log(myAssistant.whatIsTheValue());
//Assistant.js
whatIsTheValue : function(someArg) {
var deferred = someService.getSomething();
deferred.then(lang.hitch(this, this._getTheValue));
//In theory, I want to return whatever this._getTheValue returns, how can I do that?!
}
_getTheValue() {
...
...
return xyz;
}
Deferreds are asynchronous operations. Therefore you can't return a variable in a normal way from them because they will not execute until after the current function context completes.
If you want to do more with that value you will need to do so in terms of another callback (IE chaining the then statements.)
The point of deferreds is to provide sequential operations for callbacks. So you can chain them to achieve the results you want. If you need the results to be available in your current execution context, you're going to have to find a synchronous (not deferreds) method of doing what you want.
So something like this
//Assistant.js
whatIsTheValue : function(someArg) {
var deferred = someService.getSomething();
var next = deferred.then(lang.hitch(this, this._getTheValue));
next.then(/*insert next function here*/);
}
You need to understand that using a deferred lang.hitch isn't going to execute until after whatistheValue is done operating. So instead of returning the value to whatever function called whatisthevalue, you're going to have to put the logic of processing that value into a new function and use that as an additional callback for your deferred. This will probably require some restructuring of your program.
I do not know what your lang.hitch does, but the solution should look like that:
Assistant.prototype.whatIsTheValue = function(someArg) {
var deferred = someService.getSomething();
return deferred.then(lang.hitch(this, this._getTheValue));
// ^^^^^^
};
var myAssistant = new Assistant();
myAssistant.whatIsTheValue().then(console.log); // use console.log.bind(console) in Chrome
// ^^^^ - it is a promise you return
use JQuery's $when instead.
Example
// assuming both getData and getLocation return their respective Promise
var combinedPromise = $.when(getData(), getLocation())
// function will be called when both getData and getLocation resolve
combinePromise.done(function(data,location){
alert("We got data: " + dataResult + " and location: " + location);
});
http://www.html5rocks.com/en/tutorials/async/deferred/