jQuery deferreds and promises - .then() vs .done()
Asked Answered
G

12

524

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?

Gopherwood answered 25/3, 2011 at 18:2 Comment(2)
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.Barbiebarbieri
I updated my answer with an improved recommendation for what to use when.Gerome
A
602

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().

Aube answered 25/3, 2011 at 18:55 Comment(4)
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.Emptor
jQuery 3.0 is the first version that is compliant with Promises/A+ and ES2015 spec.Barbiebarbieri
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?Ligamentous
@Ligamentous Check out my answer to finally answer that question (use .then()).Gerome
P
441

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
})
Petie answered 28/3, 2013 at 23:35 Comment(12)
+1 for the notion that done does nothing to the result where then changes the result. Huge point missed by the others imo.Gaillard
It's probably worth mentioning what version of jQuery this applies to, since the behaviour of then changed in 1.8Monochromat
+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.Rembert
the above example also highlights that 'done' works on original promise object created initially but 'then' returns a new promise.Satterwhite
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.Platinocyanide
Also, jQuery 3.0 is the first version that is compliant with ES2015 and Promises/A+ spec.Barbiebarbieri
"if you return a Promise ... the outer promise will wait until inner promise resolves" <-- the words I've searched all over for.Plumbum
Please can someone review #51930134. It's been suggested this is a duplicate but the behaviour I'm getting is different to that described above.Unexpressed
Don't both then and done return chainable objects? I feel like the big difference here is that you can modify the return value with then and not with done. I think both are chainable en.wikipedia.org/wiki/Method_chainingWaters
@MarkG. It's that the returned objects are different, I haven't tested this but done should return original object which can be further chained, but then returns the promise which can also be chainedPetie
In pure JavaScript does a final 'then' leak the memory allocated for the final Promise? If not, what is the use case for 'done'? If 'done' returns the original Promise, then it is useless, since the original Promise is resolved and immutable.Barquentine
You can return a thenable from a "resolved/done" handler, and the the promise returned by .then will follow that thenable. So, in the first promise example above, just return $http.get('/some/data?value=xyz'). In other words: do not nest .then inside a .then. Chain them.Kahlil
O
60

.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?

Overstuffed answered 25/3, 2011 at 18:11 Comment(2)
You fail to mention that 'then' produces call chains. See Lu4's answer.Ritenuto
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.)Gerome
P
16

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);
}
Protrusile answered 15/3, 2015 at 3:42 Comment(2)
your post does not make clear how then behaves if no fail callback is provided - namely not capturing the fail case at allGoetz
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.Barquentine
S
11

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!)

Skulduggery answered 22/12, 2015 at 19:2 Comment(0)
M
8

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.

Mandibular answered 6/7, 2015 at 13:28 Comment(2)
"then() always means it will be called in whatever case" -- not true. then() is never called in the case of error inside the Promise.Barquentine
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.)Gerome
G
5

Only use .then()

.done() has no advantages and these disadvantages:

  • can not be chained properly
    • a.done().done() is the same as a.done(); a.done(), which can be estimated with a.then(); a.then()
    • a.then().then() is not possible with .done()
  • blocks 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
  • .then() does not have any of these problems

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
  • exception in a .done() influences resolve() caller
    • logged via catch around resolve()
  • exception breaks promise from further .done() resolution
    • 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)

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.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>
Gerome answered 1/4, 2020 at 4:40 Comment(3)
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?Ligamentous
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.Ligamentous
@Ligamentous 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.Gerome
D
4

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');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

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');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

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.

Duodiode answered 4/12, 2019 at 14:58 Comment(1)
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.Crosslink
G
3

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

Goetz answered 8/12, 2017 at 16:24 Comment(0)
K
1

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

Kahlil answered 17/2, 2021 at 19:12 Comment(0)
F
0

I was using jquery_3.5.1 and this worked perfectly for me from a jQuery.post function all I needed to do was add the 'then' promise to the end of the function. Here is a link to the promise doc on the w3c website: https://www.w3schools.com/js/js_promise.asp

Here is my code snippet that worked with jQuery.post hope this helps

$.post('../endPoint/employers/employerFindCandidateByPublicId.php', {
    param: param
}).done(function (data) {
    //..Your Stuff here
}).then(successCallback, failCallback);

function successCallback() {
    alert('Success');
}

function failCallback() {
    alert('Failed');
}
Foolproof answered 23/5, 2023 at 17:16 Comment(0)
D
-4

.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

Demean answered 30/10, 2014 at 14:27 Comment(5)
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.Pipage
My bad, did not check the jQueryDemean
@glebbahmutov - maybe you should delete this answer so that others don't get confused? Just a friendly suggestion :)Unmoved
Please don't delete the answer, this can help people clear up their misunderstandings too.Cromlech
Instead of deleting the (wrong) answer, it would be interesting to update it with why it's wrong. And it would avoid downvotes ;)Skiplane

© 2022 - 2024 — McMap. All rights reserved.