Reputation: 3210
I'm trying to chain some calls based on jquery deferred object. To make it simple, I want to :
At first, I wrote something like that :
myFirstFunction()
.progress(myFirstProgressCallback)
.done(myFirstDoneCallback)
.then(mySecondFunction)
.progress(mySecondProgressCallback)
.done(mySecondDoneCallback);
But I observed something I did not expected (after reading the docs, it seems to be the way it works) :
Example you can run it in this jsbin:
function async(number){
var def = new $.Deferred();
setTimeout(function(){
def.notify("Hello from " + number);
}, 300);
setTimeout(function(){
def.resolve("I'm done " +number);
}, 600);
return def.promise();
}
async(1)
.progress(function(msg){
console.log("First progress: " + msg);
})
.done(function(msg){
console.log("First done: " +msg);
})
.then(function(){
return async(2);
})
.progress(function(msg){
console.log("Second progress: " + msg);
})
.done(function(msg){
console.log("Second done: " +msg);
});
Result in the console:
"First progress: Hello from 1"
"Second progress: Hello from 1"
"First done: I'm done 1"
"Second progress: Hello from 2"
"Second done: I'm done 2"
First reaction : "Why the hell ??????"
Second : "How can I do what I want ?"
I replaced my code by this one, which works great (jsbin):
function async(number){
var def = new $.Deferred();
setTimeout(function(){
def.notify("Hello from " + number);
}, 300);
setTimeout(function(){
def.resolve("I'm done " +number);
}, 600);
return def.promise();
}
async(1)
.progress(function(msg){
console.log("First progress: " + msg);
})
.done(function(msg){
console.log("First done: " +msg);
})
.then(function(){
return async(2)
.progress(function(msg){
console.log("Second progress: " + msg);
})
.done(function(msg){
console.log("Second done: " +msg);
});
});
Output:
"First progress: Hello from 1"
"First done: I'm done 1"
"Second progress: Hello from 2"
"Second done: I'm done 2"
How to avoid registering the progress callback inside the function inside the "then" statement?
Upvotes: 2
Views: 453
Reputation: 2030
Here is an idea that might work for you: Check the context inside the callback.
By default the context of a callback is the promise that fired the action:
var async2,
async1 = async(1);
async1
.done(function (msg) {
if (this === async1) {
console.log("First done: " + msg);
}
})
.fail(function (msg) {
if (this === async1) {
console.log("First fail: " + msg);
}
})
.progress(function (msg) {
if (this === async1) {
console.log("First progress: " + msg);
}
})
.then(function (msg) {
async2 = async(2);
return async2;
})
.done(function (msg) {
if (this === async2) {
console.log("Second done: " + msg);
}
})
.fail(function (msg) {
if (this === async2) {
console.log("Second fail: " + msg);
}
})
.progress(function (msg) {
if (this === async2) {
console.log("Second progress: " + msg);
}
});
I'm not sure if this is a better solution than nesting the progress callback inside of the then
. One issue is that the actions maybe executed with a specific context (using notifyWith
, resolveWith
, rejectWith
).
More Info Than You Asked For
I discovered the same behavior a little while ago, felt the same frustration, and came to the same resolution you did. Since then I have done a little more research into how notify/progress works and this is what I have found:
then
returns a new promise, but it also forwards all of the actions (resolve
, reject
, notify
) from the former promise to the latter promise. In fact, as soon as you add error handling to your promise chain, you will see that this behavior extends to fail
callbacks as well:
function async(number){
var def = new $.Deferred();
setTimeout(function(){
def.notify("Hello from " + number);
}, 300);
setTimeout(function(){
def.reject("I've failed " + number);
}, 450);
return def.promise();
}
async(1)
.progress(function(msg){
console.log("First progress: " + msg);
})
.fail(function(msg){
console.log("First fail: " +msg);
})
.then(function(){
return async(2);
})
.progress(function(msg){
console.log("Second progress: " + msg);
})
.fail(function(msg){
console.log("Second fail: " +msg);
});
Output:
"First progress: Hello from 1"
"Second progress: Hello from 1"
"First fail: I've failed 1"
"Second fail: I've failed 1"
Even though the second async
is never invoked, all of the progress
and fail
callbacks have been executed. The same thing, although seldom, will occur with the done handler if you provide it with anything but a function:
async(1)
.progress(function(msg){
console.log("First progress: " + msg);
})
.done(function(msg){
console.log("First done: " +msg);
})
.then('foo')
.progress(function(msg){
console.log("Second progress: " + msg);
})
.done(function(msg){
console.log("Second done: " +msg);
});
Output:
"First progress: Hello from 1"
"Second progress: Hello from 1"
"First done: I'm done 1"
"Second done: I'm done 1"
So I guess what I am trying to say is that the behavior you are seeing with the progress callback is not inconsistent with how the Deferred object works.
On the onset of my investigation I was optimistic that we might be able to provoke the behavior we desire by using the Promises/A+ style: promise.then(doneFilter, failFilter, progressFilter)
:
async(1)
.then(function (msg) {
console.log("First done: " + msg);
return async(2);
},
null /*Failure Handler*/,
function (msg) {
console.log("First progress: " + msg);
})
.then(function (msg) {
console.log("Second done: " + msg);
},
null /*Failure Handler*/,
function (msg) {
console.log("Second progress: " + msg);
});
Unfortunately, the results are no better:
"First progress: Hello from 1"
"Second progress: undefined"
"First done: I'm done 1"
"Second progress: Hello from 2"
"Second done: I'm done 2"
Interestingly the first execution of the second progress callback is not provided the correct value. I have not investigated that further except to confirm that Q (another implementation of promises that supports progress/notify) provides identical results.
Finally, I answered a question that helped me clarify why how this all works:
If all of the actions are forwarded to the next promise, why isn't the nested progress handler invoked by those forwarded actions?
The progress
handler is added as a callback after the first promise is resolved and the next async task is pending. Unlike the done
and fail
, progress
handlers need to be attached at the time that the corresponding action (notify
) is taken.
Upvotes: 1