attempting to break jQuery promise chain with .then, .fail and .reject
Asked Answered
Z

2

12

Update: this issue was a result of jQuery 1.7 vs 1.8. Do not ever use promises in 1.7 beacuse they aren't chainable with returning a promise inside a .then. 1.8 looks like they didn't mess it up.

http://jsfiddle.net/delvarworld/28TDM/

// make a promise
var deferred = $.Deferred();
promise = deferred.promise();

// return a promise, that after 1 second, is rejected
promise.then(function(){
    var t = $.Deferred();
    setTimeout(function() {
        console.log('rejecting...');
        t.reject();
    }, 1000);

    return t.promise();
});

// if that promise is successful, do this
promise.then(function() {
    console.log('i should never be called');
})

// if it errors, do this
promise.fail(function() {
    console.log('i should be called');
});

deferred.resolve();

Expected: 'i should be called'

Actual: 'i should never be called'

Problem: I want to chain callbacks and have any one of them be able to break the chain and trigger the fail function, and skip the other chained callbacks. I don't understand why all of the thens are triggered and the fail is not triggered.

I'm coming from NodeJS's Q library, so I tried it with .then first. However, changing it to .pipe has no effect.

Zaccaria answered 27/8, 2012 at 21:41 Comment(4)
Returning t from .then() doesn't do anything. Rejecting t also doesn't affect the original deferred object in any way. Callbacks from .then aren't called until after the deferred object they are applied to are either resolved or rejected. Once they are resolved or rejected, they can't be rejected or resolved again.Alfalfa
To make this easier to understand, replace .then with .done since in your case they do exactly the same thing.Alfalfa
At least in Q, I believe, returning a promise inside of then adds it to the chain. what's the correct way to chain otherwise?Zaccaria
Related: Problems inherent to jQuery $.Deferred, especially when you come with your expectations from Q.Nevsa
A
12

You aren't re-defining the value of promise, try this:

http://jsfiddle.net/28TDM/1/

var deferred = $.Deferred();
promise = deferred.promise();

promise = promise.then(function(){
    var t = $.Deferred();
    setTimeout(function() {
        console.log('rejecting...');
        t.reject();
    }, 1000);

    return t.promise();
});

promise.then(function() {
    console.log('i should never be called');
})

promise.fail(function() {
    console.log('i should be called');
});

deferred.resolve();

Apparently it does work the way you thought it did, it just isn't documented https://api.jquery.com/deferred.then. Very cool. This is new functionality added in jQuery 1.8.0, more than likely they just aren't done updating the documentation.

Alfalfa answered 27/8, 2012 at 22:3 Comment(10)
i'm still having an issue with this flow but having trouble making a generalized jsfiddle for it. i'm wondering if this fiddle is just working from coincidence.Zaccaria
@AndyRay If it helps any, this is the change that made my fiddle work: bugs.jquery.com/ticket/11010Alfalfa
Also, if you use .pipe in place of .then it will also work. jsfiddle.net/28TDM/2 Then, if you chain it rather than going back to the promise variable, you don't have to re-define promise. jsfiddle.net/28TDM/3Alfalfa
UPDATE: I have found the actual issue. We were using jQuery 1.7. Promises in 1.7 are broken and awful. They are fixed in 1.8 and it looks like pipe is deprecated, thank god.Zaccaria
i should never be called IS called. you don't really need a demo page since you can just copy the code to the browser inspector and run it..Sainted
@Sainted that depends on what website you're on. SO here still uses jquery 1.7.1 and the answer is for jquery 1.8+Alfalfa
@KevinB - checked on my website which has 1.11.. it first says rejecting... then i should never be calledSainted
That's not what i see here: jsfiddle.net/28TDM/14 does $.fn.jquery give you 1.11?Alfalfa
i'm confused. your image shows exactly what it should. rejecting... then i should be calledAlfalfa
haa opss sorry, my brain read it as "I should never be called". sorry again. excellent work!Sainted
R
1

IMHO, you're not chaining anything. Your 2nd .then is attached to the same promise as that the first .then is attached to.

Why?

Notice that, then will always RETURN the new promise, rather than change the promise it being attached to. It doesn't have side effect.

For example:

var promiseX = promiseA
                 .then(function() { return promiseB; })
promiseX.then(function() { return promiseC; });

promiseA won't change its value after being attached then; it'll keep as is.

promiseX will be the return value of the 1st then, that is, promiseB.

So the 2nd then is actually attached to promiseB.

And this is exactly what @Kevin B did in his answer.


Another solution is, since .then will return the new promise, you can chain the .then functions like below.

var promiseX = promiseA
                 .then(function() { return promiseB; })
                 .then(function() { return promiseC; });

This time, the 1st then is attached to promiseA, and guess to which promise is the 2nd then being attached to?

You're right. It's promiseB, not promiseA. Because the 2nd then is actually being attached to the return value of the 1st then, i.e., promiseB.

And finally the 2nd then's return value is assigned to promiseX, so promiseX equals promiseC.

Ok, get back to the OP's question. The following code is my answer.

var deferred = $.Deferred();
promise = deferred.promise(); // this is the first promise

promise.then(function(){ // callbacks for 1st promise
    var t = $.Deferred();
    setTimeout(function() {
        console.log('rejecting...');
        t.reject();
    }, 1000);
    return t.promise(); // this is the 2nd promise
    // return $.Deferred().reject(); // To reject immediately.
}).then(function() { // callbacks for 2nd promise
    console.log('i should never be called');
}, function() {
    console.log('i should be called');
})

deferred.resolve();
Ruelu answered 7/9, 2015 at 7:28 Comment(9)
The downvote is probably because .then returns a new promise. You are incorrect.Pas
Of course .then return a new promise, I knew it. But look at the example from OP, i should never be called is attached to the promise corresponding to deferred not t. But the OP expected that he/she rejected the promise t and the 2nd then is attached to t.promise (but it's not). That's what I meant. Look at my code, the 2nd then is attached to t.promise, that's exactly what the OP expected. Isn't it?Ruelu
Oh. The wording of your second sentence in the answer seems to imply otherwise. The second .then is not attached to the same promise as the first then. Even if the first "then callback" didn't return a promise, the second then would still be attached to a different Deferred instance, because .then always returns a new deferred. Example on jsfiddle.Pas
Oh! I think the downvote was from someone that misunderstood that sentence the same way I did. You meant the OP's second then is attached to the same promise. It is easy to mistakenly interpret your statement as saying .then chains like a selector, where the original promise chains though. +1.Pas
@doug65536. Your comment before last comment is wrong. Yes, .then always return a new promise, but OP didn't assign the returned promise to any variable, so the variable promise will still be equal to deferred.promise() and hence 2nd then is attached to deferred.promise(). And that's why @Kevin B, assigned the promise returned by the first then back to promise, which updates the variable promise to t.promise(). In my answer, the first then returns a new promise (t.promise), to which the 2nd then is attached. Thanks for your upvote ;)Ruelu
@Pas Thanks for pointing out the problem. I'm not a native English speaker so maybe my answer was misleading. I elaborated what I meant in the updated answer.Ruelu
I gave up on javascript questions a while ago. Too many Duning Kruger "experts" roaming around. Correct js answers get downvoted all the time.Pas
The 10 upvote accepted answer is an "oh neat that does work!" with one assignment added in and complaint about documentation. Would be nice if he removed the misinformation comments on the question though.Pas
Thanks. The accepted answer does work, so does mine. I also explained some details about then and the 2nd solution "then chain". I suspect the downvoter didn't look at my solution carefully. Thanks for listening to my complain ;)Ruelu

© 2022 - 2024 — McMap. All rights reserved.