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

303,142

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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);
}

Solution 5

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

Share:
303,142
screenm0nkey
Author by

screenm0nkey

Updated on July 08, 2022

Comments

  • screenm0nkey
    screenm0nkey almost 2 years

    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?

  • Shanimal
    Shanimal about 11 years
    +1 for the notion that done does nothing to the result where then changes the result. Huge point missed by the others imo.
  • bradley.ayers
    bradley.ayers about 11 years
    It's probably worth mentioning what version of jQuery this applies to, since the behaviour of then changed in 1.8
  • oligofren
    oligofren over 10 years
    You fail to mention that 'then' produces call chains. See Lu4's answer.
  • Michael Kropat
    Michael Kropat over 10 years
    +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.
  • Pulak Kanti Bhattacharyya
    Pulak Kanti Bhattacharyya almost 10 years
    the above example also highlights that 'done' works on original promise object created initially but 'then' returns a new promise.
  • David Harkness
    David Harkness almost 10 years
    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.
  • Roamer-1888
    Roamer-1888 over 9 years
    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.
  • gleb bahmutov
    gleb bahmutov over 9 years
    My bad, did not check the jQuery
  • wrschneider
    wrschneider over 9 years
    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.
  • Andrey
    Andrey over 8 years
    @glebbahmutov - maybe you should delete this answer so that others don't get confused? Just a friendly suggestion :)
  • Melissa
    Melissa over 8 years
    Please don't delete the answer, this can help people clear up their misunderstandings too.
  • Flimm
    Flimm almost 8 years
    Also, jQuery 3.0 is the first version that is compliant with ES2015 and Promises/A+ spec.
  • Flimm
    Flimm almost 8 years
    jQuery 3.0 is the first version that is compliant with Promises/A+ and ES2015 spec.
  • Bob Stein
    Bob Stein almost 7 years
    "if you return a Promise ... the outer promise will wait until inner promise resolves" <-- the words I've searched all over for.
  • B M
    B M over 6 years
    your post does not make clear how then behaves if no fail callback is provided - namely not capturing the fail case at all
  • Andy
    Andy almost 6 years
    Please can someone review stackoverflow.com/questions/51929634/…. It's been suggested this is a duplicate but the behaviour I'm getting is different to that described above.
  • Mark G.
    Mark G. over 5 years
    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_chaining
  • Lu4
    Lu4 over 5 years
    @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 chained
  • CodingYoshi
    CodingYoshi over 5 years
    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?
  • David Spector
    David Spector over 5 years
    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.
  • David Spector
    David Spector over 5 years
    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.
  • David Spector
    David Spector over 5 years
    "then() always means it will be called in whatever case" -- not true. then() is never called in the case of error inside the Promise.
  • Robert Siemer
    Robert Siemer about 4 years
    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.)
  • Robert Siemer
    Robert Siemer about 4 years
    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.)
  • Robert Siemer
    Robert Siemer about 4 years
    @CodingYoshi Check out my answer to finally answer that question (use .then()).
  • CodingYoshi
    CodingYoshi about 4 years
    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?
  • CodingYoshi
    CodingYoshi about 4 years
    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.
  • Robert Siemer
    Robert Siemer about 4 years
    @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.
  • Robert Siemer
    Robert Siemer about 4 years
    @CodingYoshi I greatly improved my answer, if I’m allowed to say. Code and text.
  • Shant Dashjian
    Shant Dashjian about 4 years
    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.
  • Stephan van Hoof
    Stephan van Hoof over 3 years
    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.
  • AFract
    AFract over 2 years
    Instead of deleting the (wrong) answer, it would be interesting to update it with why it's wrong. And it would avoid downvotes ;)