Asked  7 Months ago    Answers:  5   Viewed   75 times

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?

 Answers

91

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

Tuesday, June 1, 2021
 
CodeCaster
answered 7 Months ago
45

Very astute, jQuery.when is what you're looking for. It takes either a promise or a normal non-thenable and returns a promise over that value. However, since you have functions and not values here that isn't really required since this is the behavior of .then anyway.

var queue = [syncFunction, syncFunc2, returnsPromiseAsync];

var d = $.Deferred().resolve();
while (queue.length > 0) {
   d = d.then(queue.shift()); // you don't need the `.done`
}

(fiddle)

Alternatively, you can reduce;

var res = queue.reduce(function(prev,cur){ // chain to res later to hook on done
    return prev.then(cur);
}, $.Deferred().resolve());

If you have more code doing things below this, since the functions in the queue may run synchronously or asynchronously (or some synchronous and then we hit an async one), to avoid chaos you may want to ensure that the functions always run asynchronously. You can easily do that by wrapping the resolve on the initial Deferred in a setTimeout:

var p = $.Deferred();
var res = queue.reduce(function(prev,cur){ // chain to res later to hook on done
    return prev.then(cur);
}, p);
setTimeout(p.resolve.bind(p), 0);

Now the process won't start until the timeout occurs, and any code following this will definitely run before the first function from the queue does. In my view, that's inconsistent with the semantics of jQuery's promises (because they are inconsistent about sync vs. async callbacks), but you may want the consistency.

If you need to know when the process completes, just use a .then handler on res:

res.then(function() {
    // All functions have now completed
});

For those situations, here's a simple wrapper function that does both:

function clearQueue(q) {
    var p = $.Deferred();
    setTimeout(p.resolve.bind(p), 0);

    return q.reduce(function(prev,cur){ 
        return prev.then(cur);
    }, p);
}

Example use (fiddle):

clearQueue(queue).then(function() {
    console.log("All done");
});
console.log("This runs before any queued function starts");
Saturday, July 3, 2021
 
tdous
answered 5 Months ago
89

First off, you have to decide if you want your three ajax calls to be processed in parallel (running all at the same time, with less overall running time) or in sequence where one ajax calls runs, completes and then you launch the next ajax call. This is a key design decision that impacts how you do this.

When you use $.when() you are launching all three ajax calls in parallel. If you examine the results only when all have completed, you can still process the results in a specific order (since you will be processing them only when all results are available and they will be available in the order requested). But, when doing it this way all the ajax calls will be initially sent at once. This will give you a better end-to-end time so if this is feasible for the types of requests, this is generally a better way to do it.

To do that, you can restructure what you have to something like this:

Run in Parallel

var files = [
   'url1', 'url2', 'url3'
];

$.when($.ajax(files[0]),$.ajax(files[1]),$.ajax(files[2])).done(function(a1, a2, a3) {
   var results = [];
   results.push(a1[0]);
   results.push(a2[0]);
   results.push(a3[0]);
   console.log("got all results")
});

Because you're waiting until the .done() handler for $.when() has been called, all the ajax results are ready at once and they are presented by $.when() in the order they were requested (regardless of which one actually finished first), so you get the results as quick as possible and they are presented in a predictable order.

Note, I also moved the definition of the results array into the $.when() done handler because that's the only place you know the data is actually valid (for timing reasons).


Run in Parallel - Iterate Arbitrary Length Array

If you had a longer array, you might find it better to iterate through your array with something like .map() to process them all in a loop rather than listing them individually:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

$.when.apply($, files.map(function(url) {
    return $.ajax(url);
})).done(function() {
    var results = [];
    // there will be one argument passed to this callback for each ajax call
    // each argument is of this form [data, statusText, jqXHR]
    for (var i = 0; i < arguments.length; i++) {
        results.push(arguments[i][0]);
    }
    // all data is now in the results array in order
});

Sequence the Ajax Calls

If, on the other hand, you actually want to sequence your ajax calls so the 2nd one doesn't start until the first one finishes (something that may be required if the 2nd ajax call needs results from the 1st ajax call in order to know what to request or do), then you need a completely different design pattern and $.when() is not the way to go at all (it only does parallel requests). In that case, you probably just want to chain your results with x.then().then() and you can then output the log statements in the sequence you asked for like this.

  $.ajax(files[0]).then(function(data0) {
      console.log("step 1.0");
      return $.ajax(files[1]);
  }).then(function(data1) {
      console.log("step 1.1");
      return $.ajax(files[2]);
  }).done(function(data2) {
      console.log("step 1.2");
      // all the ajax calls are done here
      console.log("step 2");
  });

Console Output:

step 1.0
step 1.1
step 1.2
step 2

This structure can also be put into a loop to automatically run it for N sequential ajax calls if your array of files is longer. While you could collect the results as you go into the results array, often the reason things are done sequentially is that the prior results are consumed by the next ajax call so you often only need the final result. If you wanted to collect the results as you go, you could certainly push them into the results array at each step.

Notice, the advantages that promises offer here in that you can sequence operations while staying at the same top level of nesting and not getting further and further nested.


Sequence the Ajax Calls - Iterate Arbitrary Length Array

Here's what the sequencing would look like in a loop:

var files = [
   'url1', 'url2', 'url3', 'url4', 'url5', 'url6', 'url7'
];

var results = [];
files.reduce(function(prev, cur, index) {
    return prev.then(function(data) {
        return $.ajax(cur).then(function(data) {
            console.log("step 1." + index);
            results.push(data);
        });
    })
}, $().promise()).done(function() {
    // last ajax call done
    // all results are in the results array
    console.log("step 2.0");
});

Console Output:

step 1.0
step 1.1
step 1.2
step 1.3
step 1.4
step 1.5
step 1.6
step 2

The Array.prototype.reduce() method works handily here because it accumulates a single value as you process each individual array element which is what you need to do as you add .then() for each array element. The .reduce() iteration is started with an empty/resolved promise with $().promise() (there are other ways to also create such a promise) which just gives us something to start doing .then() on that is already resolved.

Thursday, July 29, 2021
 
wavyGravy
answered 4 Months ago
84
function timeout(funct, args, time) {
    var deferred = new jQuery.Deferred(),
        promise = funct.apply(null, args);

    if (promise) {
        $.when(promise)
            .done(deferred.resolve)
            .fail(deferred.reject)
            .progress(deferred.notify);
    }

    setTimeout(function() {
        deferred.reject();
    }, time);

    return deferred.promise();
}
Tuesday, August 3, 2021
 
rampy
answered 4 Months ago
45

.done() has only success callback.

.then() has both success and fail callbacks.

As of jQuery 1.8, the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function, replacing the now-deprecated deferred.pipe() method.

The deferred.done() method accepts one or more arguments, all of which can be either a single function or an array of functions.

Since deferred.done() returns the deferred object, other methods of the deferred object can be chained to this one, including additional .done() methods. When the Deferred is resolved, doneCallbacks are executed using the arguments provided to the resolve or resolveWith method call in the order they were added.

Monday, August 9, 2021
 
Shurmajee
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share