ChatGPT解决这个技术问题 Extra ChatGPT

jQuery deferreds and promises - .then() vs .done()

I've been reading about jQuery deferreds and promises and I can't see the difference between using .then() & .done() for successful callbacks. I know Eric Hynds mentions that .done() and .success() map to the same functionality but I'm guessing so does .then() as all the callbacks are all invoked on a completion of a successful operation.

Can anyone please enlighten me to the correct usage?

Please note everybody that JQuery 3.0 released in June 2016 was the first version that was compliant with Promises/A+ and ES2015 Promises spec. The implementation before that had incompatibilities with what promises were supposed to deliver.
I updated my answer with an improved recommendation for what to use when.

C
Community

The callbacks attached to done() will be fired when the deferred is resolved. The callbacks attached to fail() will be fired when the deferred is rejected.

Prior to jQuery 1.8, then() was just syntactic sugar:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

As of 1.8, then() is an alias for pipe() and returns a new promise, see here for more information on pipe().

success() and error() are only available on the jqXHR object returned by a call to ajax(). They are simple aliases for done() and fail() respectively:

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Also, done() is not limited to a single callback and will filter out non-functions (though there is a bug with strings in version 1.8 that should be fixed in 1.8.1):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

Same goes for fail().


then returning a new promise was a key thing I was missing. I couldn't understand why a chain like $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... }) was failing with data2 undefined; when I changed done to then it worked, because I was really wanting to pipe promises together rather than attach more handlers to the original promise.
jQuery 3.0 is the first version that is compliant with Promises/A+ and ES2015 spec.
I still don't understand why I would use one over the other. If I make an ajax call and I need to wait until that call has been fully completed (meaning reponse is returned from server) before I call another ajax call, do I use done or then? Why?
@CodingYoshi Check out my answer to finally answer that question (use .then()).
L
Lu4

There is also difference in way that return results are processed (its called chaining, done doesn't chain while then produces call chains)

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

The following results will get logged:

abc
123
undefined

While

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

will get the following:

abc
abc
abc

---------- Update:

Btw. I forgot to mention, if you return a Promise instead of atomic type value, the outer promise will wait until inner promise resolves:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

in this way it becomes very straightforward to compose parallel or sequential asynchronous operations such as:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

The above code issues two http requests in parallel thus making the requests complete sooner, while below those http requests are being run sequentially thus reducing server load

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

+1 for the notion that done does nothing to the result where then changes the result. Huge point missed by the others imo.
It's probably worth mentioning what version of jQuery this applies to, since the behaviour of then changed in 1.8
+1 Straight to the point. I created a runnable example if anyone wants to see what chains with mixed done and then calls results in.
the above example also highlights that 'done' works on original promise object created initially but 'then' returns a new promise.
This applies to jQuery 1.8+. Older versions act just like the done example. Change then to pipe in pre-1.8 to get the 1.8+ then behavior.
w
whoan

.done() has only one callback and it is the success callback

.then() has both success and fail callbacks

.fail() has only one fail callback

so it is up to you what you must do... do you care if it succeeds or if it fails?


You fail to mention that 'then' produces call chains. See Lu4's answer.
Your answer is from 2011... Nowadays their return values makes then() very different from done(). As then() is often called only with the success callback your point is rather a detail than the main thing to remember/know. (Can’t say how it was before jQuery 3.0.)
N
Nipuna

deferred.done()

adds handlers to be called only when Deferred is resolved. You can add multiple callbacks to be called.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

You can also write above like this,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then()

adds handlers to be called when Deferred is resolved, rejected or still in progress.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}

your post does not make clear how then behaves if no fail callback is provided - namely not capturing the fail case at all
The fail case raises an exception that can be caught by the top level of the program. You can also see the exception in the JavaScript console.
C
Community

There is actually a pretty critical difference, insofar as jQuery's Deferreds are meant to be an implementations of Promises (and jQuery3.0 actually tries to bring them into spec).

The key difference between done/then is that

.done() ALWAYS returns the same Promise/wrapped values it started with, regardless of what you do or what you return.

.then() always returns a NEW Promise, and you are in charge of controlling what that Promise is based on what the function you passed it returned.

Translated from jQuery into native ES2015 Promises, .done() is sort of like implementing a "tap" structure around a function in a Promise chain, in that it will, if the chain is in the "resolve" state, pass a value to a function... but the result of that function will NOT affect the chain itself.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Those will both log 5, not 6.

Note that I used done and doneWrap to do logging, not .then. That's because console.log functions don't actually return anything. And what happens if you pass .then a function that doesn't return anything?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

That will log:

5 undefined

What happened? When I used .then and passed it a function that didn't return anything, it's implicit result was "undefined"... which of course returned a Promise[undefined] to the next then method, which logged undefined. So the original value we started with was basically lost.

.then() is, at heart, a form of function composition: the result of each step is used as the argument for the function in the next step. That's why .done is best thought of as a "tap"-> it's not actually part of the composition, just something that sneaks a look at the value at a certain step and runs a function at that value, but doesn't actually alter the composition in any way.

This is a pretty fundamental difference, and there's a probably a good reason why native Promises don't have a .done method implemented themselves. We don't eve have to get into why there's no .fail method, because that's even more complicated (namely, .fail/.catch are NOT mirrors of .done/.then -> functions in .catch that return bare values do not "stay" rejected like those passed to .then, they resolve!)


J
JasmineOT

then() always means it will be called in whatever case. But the parameters passing are different in different jQuery versions.

Prior to jQuery 1.8, then() equals done().fail(). And all of the callback functions share same parameters.

But as of jQuery 1.8, then() returns a new promise, and if it has return a value, it will be passed into the next callback function.

Let's see the following example:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

Prior to jQuery 1.8, the answer should be

result = 3
result = 3
result = 3

All result takes 3. And then() function always passes the same deferred object to the next function.

But as of jQuery 1.8, the result should be:

result = 3
result = 7
result = NaN

Because the first then() function returns a new promise, and the value 7 (and this is the only parameter that will passed on)is passed to the next done(), so the second done() write result = 7. The second then() takes 7 as the value of a and takes undefined as the value of b, so the second then() returns a new promise with the parameter NaN, and the last done() prints NaN as its result.


"then() always means it will be called in whatever case" -- not true. then() is never called in the case of error inside the Promise.
Interesting aspect that a jQuery.Deferred() can receive multiple values, which it properly passes on to the first .then().—A little strange though... as any following .then() can’t do so. (The chosen interface via return can only return one value.) Javascript’s native Promise doesn’t do that. (Which is more consistent, to be honest.)
R
Robert Siemer

Only use .then()

These are the disadvantages of .done()

can not be chained

block resolve() call (all .done() handlers will be executed synchronous)

resolve() might get an exception from registered .done() handlers(!)

an exception in a .done() half-kills the deferred: further .done() handlers will be silently skipped

further .done() handlers will be silently skipped

I thought temporarily that .then(oneArgOnly) always requires .catch() so that no exception gets silently ignored, but that is not true any more: the unhandledrejection event logs unhandled .then() exceptions on the console (as default). Very reasonable! No reason left to use .done() at all.

Proof

The following code snippet reveals, that:

all .done() handlers will be called synchronous at point of resolve() logged as 1, 3, 5, 7 logged before the script falls through bottom

logged as 1, 3, 5, 7

logged before the script falls through bottom

exception in a .done() influences resolve() caller logged via catch around resolve()

logged via catch around resolve()

exception breaks promise from further .done() resolution 8 and 10 are not logged!

8 and 10 are not logged!

.then() has none of these problems logged as 2, 4, 6, 9, 11 after thread turns idle (snippet environment has no unhandledrejection is seems)

logged as 2, 4, 6, 9, 11 after thread turns idle

(snippet environment has no unhandledrejection is seems)

Btw, exceptions from .done() can’t be properly caught: because of the synchronous pattern of .done(), the error is either thrown at the point of .resolve() (might be library code!) or at the .done() call which attaches the culprit if the deferred is already resolved.

console.log('Start of script.'); let deferred = $.Deferred(); // deferred.resolve('Redemption.'); deferred.fail(() => console.log('fail()')); deferred.catch(()=> console.log('catch()')); deferred.done(() => console.log('1-done()')); deferred.then(() => console.log('2-then()')); deferred.done(() => console.log('3-done()')); deferred.then(() =>{console.log('4-then()-throw'); throw 'thrown from 4-then()';}); deferred.done(() => console.log('5-done()')); deferred.then(() => console.log('6-then()')); deferred.done(() =>{console.log('7-done()-throw'); throw 'thrown from 7-done()';}); deferred.done(() => console.log('8-done()')); deferred.then(() => console.log('9-then()')); console.log('Resolving.'); try { deferred.resolve('Solution.'); } catch(e) { console.log(`Caught exception from handler in resolve():`, e); } deferred.done(() => console.log('10-done()')); deferred.then(() => console.log('11-then()')); console.log('End of script.');


A few things: 1) I see what you are saying that done will not be executed if a previous done has an exception. But why would it be silently ignored, I mean an exception occurred so why do you say it is silent. 2) I despise the Deferred object because its API is very very poorly done. It is too complex and confusing. Your code here does not help either to prove your point and it has too much un-needed complexity for what you are trying to prove. 3) Why are the done at index 2, 4, and 6 performed before the 2nd then?
My bad, ya definitely deserves a vote. As for your comment about the exception, normally that's how exceptions work: once raised, code after it won't be executed. Plus the jQuery documentation states that it will only be executed if the deferred is resolved.
@CodingYoshi The situation is different here: I was only talking about resolved promises/deferreds. I’m not complaining that the rest of the success-handler is not called, that is normal. But I see no reason why a completely different success-handler on a successful promise is not called. All .then() will be called, exception (in those handlers) raised or not. But addition/remaining .done() break.
@CodingYoshi I greatly improved my answer, if I’m allowed to say. Code and text.
B
B M

There is a very simple mental mapping in response that was a bit hard to find in the other answers:

done implements tap as in bluebird Promises

then implements then as in ES6 Promises


s
schellmax

There's one more vital difference as of jQuery 3.0 that can easily lead to unexpected behaviour and isn't mentioned in previous answers:

Consider the following code:

let d = $.Deferred(); d.done(() => console.log('then')); d.resolve(); console.log('now');

this will output:

then
now

Now, replace done() by then() in the very same snippet:

var d = $.Deferred(); d.then(() => console.log('then')); d.resolve(); console.log('now');

output is now:

now
then

So, for immediatly resolved deferreds, the function passed to done() will always be invoked in a synchronous manner, whereas any argument passed to then() is invoked async.

This differs from prior jQuery versions where both callbacks get called synchronously, as mentioned in the upgrade guide:

Another behavior change required for Promises/A+ compliance is that Deferred .then() callbacks are always called asynchronously. Previously, if a .then() callback was added to a Deferred that was already resolved or rejected, the callback would run immediately and synchronously.


Thank you. This answer explained the behavior I was seeing. I was using then(). My test was failing because the callback was called async, after the test was over. Using done() the callback is called synchronously, satisfying the test expectations, and the test passes.
S
Stephan van Hoof

In addition to the answers above:

The real power of .then is the possibility to chain ajax calls in a fluent way, and thus avoiding callback hell.

For example:

$.getJSON( 'dataservice/General', {action:'getSessionUser'} )
    .then( function( user ) {
        console.log( user );
        return $.getJSON( 'dataservice/Address', {action:'getFirstAddress'} );
    })
    .then( function( address ) {
        console.log( address );
    })

Here the second .then follows the returned $.getJSON


g
gleb bahmutov

.done() terminates the promise chain, making sure nothing else can attach further steps. This means that the jQuery promise implementation can throw any unhandled exception, since no one can possible handle it using .fail().

In practical terms, if you do not plan to attach more steps to a promise, you should use .done(). For more details see why promises need to be done


Caution! This answer would be correct for several promise implementations but not jQuery, in which .done() does not have a terminating role. The documentation says, "Since deferred.done() returns the deferred object, other methods of the deferred object can be chained to this one, including additional .done() methods". .fail() isn't mentioned but, yes, that could be chained too.
My bad, did not check the jQuery
@glebbahmutov - maybe you should delete this answer so that others don't get confused? Just a friendly suggestion :)
Please don't delete the answer, this can help people clear up their misunderstandings too.
Instead of deleting the (wrong) answer, it would be interesting to update it with why it's wrong. And it would avoid downvotes ;)