Reputation: 115
I have the following jQuery code:
myFunc: function(cmd, obj){
var idToExtMap = this. map;
var requireRes = this.reqInst;
var deferreds = [];
var ret = true;
$.each(idToExtMap[cmd], function(key, ext){
if(ext.$classIns && ext.$classIns.prepare) {
var returnedValue = ext.$classIns.prepare(obj);
deferreds.push(returnedValue);
$.when(returnedValue).done(function(satisfied){
if(ret!==false){
ret = satisfied;
}
});
} else {
requireRes(ext).done(function(){
var cls = $.toFunction(ext.$jscls);
if(cls) {
ext.$classIns = new cls();
if(ext.$classIns.prepare){
var returnedValue = ext.$classIns.prepare(obj);
deferreds.push(returnedValue);
$.when(returnedValue).done(function(satisfied){
if(ret!==false){
ret = satisfied;
}
});
}
}
});
}
});
$.when.apply(null, deferreds).done(function(){
return ret;
});
}
The problem I am having is that $.when.apply is executed before all the deferreds are pushed into deferreds array. How can I make sure that $.when.apply is only executed once all the deferreds are pushed into the deferreds array?
Upvotes: 0
Views: 71
Reputation: 19288
The main thing you have to do is ensure that promises are pushed onto the array synchronously. With deferreds.push(...)
burried inside a done callback, the push()
is asynchronous and the array is guaranteed still to be empty when $.when.apply(...)
is executed.
Some other issues can also be fixed :
ext.$classIns.prepare(obj)
can be avoided by rearrangement.ret
can be avoided by exploiting promise rejection.With some other minor tidying, I ended up with this (untested) :
myFunc: function(cmd, obj) {
var requireRes = this.reqInst;
var promises = $.map(this.map[cmd], function(key, ext) {
var p; // p for promise
if(ext.$classIns) {
p = $.when(ext.$classIns);
} else {
p = requireRes(ext).then(function() {
var cls = $.toFunction(ext.$jscls);
if(cls) {
ext.$classIns = new cls();
}
return ext.$classIns || null;
});
}
/* At this point, `p` is a promise resolved with either a previously created `cls()` object or a freshly created one */
// As we are inside `$.map(...)`, `return p.then(...)` causes the promise delivered by `p.then(...)` to be pushed onto the `promises` array.
return p.then(function($classIns) {
if($classIns && $classIns.prepare) {
return $classIns.prepare(obj).then(function(satisfied) {
if(!satisfied) {
// Here, returning a rejected promise is equivalent to setting the original `ret` irrevocably to `false`.
return $.Deferred().reject(new Error(".prepare() not satisfied"));
}
});
} else {
// You may want also to reject if `$classIns` or `$classIns.prepare` doesn't exist.
// If not, then delete this else{} clause.
return $.Deferred().reject(new Error(".prepare() could not be called"));
}
});
});
/* At this point, `promises` is an array of promises - some or all resolved, some or all pending */
// Here, instead of the boolean `ret`, you can exploit the joined promise's success/error paths :
// * success path is equivalent to ret == true.
// * error path is equivalent to ret == false, or any unpredicted error.
return $.when.apply(null, promises).then(function() {
// Success.
// Do whatever is necessary here or in `myFunc().then(function() {...})`.
}, function(e) {
// An error occurred.
// Do whatever is necessary here or in `myFunc().then(null, function() {...})`.
console.error(e); //for example
});
}
Comments should explain what's going on.
Upvotes: 1
Reputation: 707606
Move $.when.apply()
outside/after the .each()
loop so you don't call it until after you're done building the deferreds
array.
You have other issues though. I looks like you're trying to return a value from $.when().done()
. As you have your code structured, that won't do anything. You will need to either return a promise from your function or add a callback to your function that you can then call when you have your final result. This is because your operations are asynchronous and will finish long after your main function returns.
Upvotes: 0