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

JqueryPromiseJquery Deferred

Jquery Problem Overview


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?

Jquery Solutions


Solution 1 - Jquery

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 - Jquery

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 - Jquery

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

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 - Jquery

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

Solution 6 - Jquery

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.

Solution 7 - Jquery

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

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>

Solution 8 - Jquery

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

Solution 9 - Jquery

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.

Solution 10 - Jquery

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

Solution 11 - Jquery

.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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionscreenm0nkeyView Question on Stackoverflow
Solution 1 - JqueryJulian AubourgView Answer on Stackoverflow
Solution 2 - JqueryLu4View Answer on Stackoverflow
Solution 3 - JqueryMarino ŠimićView Answer on Stackoverflow
Solution 4 - JqueryNipunaView Answer on Stackoverflow
Solution 5 - JqueryDtipsonView Answer on Stackoverflow
Solution 6 - JqueryJasmineOTView Answer on Stackoverflow
Solution 7 - JqueryRobert SiemerView Answer on Stackoverflow
Solution 8 - JqueryB MView Answer on Stackoverflow
Solution 9 - JqueryschellmaxView Answer on Stackoverflow
Solution 10 - JqueryStephan van HoofView Answer on Stackoverflow
Solution 11 - Jquerygleb bahmutovView Answer on Stackoverflow