jQuery Deferred and Promises: Managing Asynchronous Operations
How I use jQuery Deferred and promises to tame async code: resolve, reject and notify, .then() chaining, $.when for parallel requests, fail-fast behaviour, and the exception trap that differs from Promises/A+.

The question I get: “I have three AJAX calls, and I need to act when all of them finish. Or the second call needs the answer from the first. My code has turned into callbacks inside callbacks. What is the tidy way?” The tidy way is a jQuery Deferred. You have used one already, because every $.ajax() call returns one.
A jQuery Deferred is jQuery’s take on the “promise” idea: an object that stands for a result that does not exist yet. The Deferred object documentation lists the full API. I cover the methods that matter in practice and run each one on jQuery 1.9.1, with the real output. The .done() and .fail() callbacks will look familiar from my jQuery AJAX tutorial.
A note on terms. People are writing promise standards right now. The community has the Promises/A+ specification. The next version of JavaScript plans a built-in promise object too, but browsers do not ship it yet. So for production code today, jQuery’s Deferred is the tool most sites already load. Where jQuery differs from the standard, I say so.
Why I name async work with jQuery Deferred
My position: use Deferreds to name the things your page waits for. A function that returns a promise documents itself: “this takes time and tells you when it is done.” Callers can add success, failure and cleanup without the function knowing about any of them. That beats passing a callback argument, and it costs nothing extra.
The jQuery Deferred lifecycle: pending, then settled once
var d = $.Deferred();
console.log('state at start :', d.state());
d.progress(function (pct) { console.log('progress :', pct + '%'); })
.done(function (value) { console.log('done :', value); })
.fail(function (reason) { console.log('fail :', reason); })
.always(function () { console.log('always : state is', d.state()); });
d.notify(25);
d.notify(75);
d.resolve('finished');
console.log('state at end :', d.state());
d.reject('too late'); // ignored: a Deferred settles once
console.log('after reject() :', d.state());
state at start : pending
progress : 25%
progress : 75%
done : finished
always : state is resolved
state at end : resolved
after reject() : resolved
A jQuery Deferred starts pending and moves exactly once, to resolved (success) or rejected (failure). You attach .done() for resolve, .fail() for reject and .always() for both. .progress() handles the optional notify calls that report progress along the way. The last two lines show the rule that makes Deferreds reliable. After it resolved, jQuery ignored the later reject(). A Deferred cannot change its mind.
Callbacks attached late still run
var d = $.Deferred().resolve('already done');
d.done(function (v) { console.log('attached after resolve, still runs:', v); });
console.log('this line prints AFTER the callback (callbacks are synchronous)');
attached after resolve, still runs: already done
this line prints AFTER the callback (callbacks are synchronous)
Once a Deferred has settled, a callback you add later runs immediately with the stored value. That separates a Deferred from an ordinary event. You cannot “miss” it by attaching too late. A function can also cache its promise and hand the same one to every caller. Note the second line of output, though. The callback ran synchronously, before the next line of your code. This version of jQuery does not push callbacks to a later turn of the event loop. That is the other difference from Promises/A+. It changes the order of things if your callbacks rely on setup code that runs afterwards.
Chaining with .then()
$.Deferred().resolve(2)
.then(function (n) { return n * 3; })
.then(function (n) { return n + 1; })
.done(function (n) { console.log('chained value:', n); });
// A callback that returns a promise makes the chain wait for it
$.Deferred().resolve('start')
.then(function () {
var next = $.Deferred();
setTimeout(function () { next.resolve('after a delay'); }, 100);
return next.promise();
})
.done(function (v) { console.log('waited for returned promise:', v); });
// Rejection skips the done-filters
$.Deferred().reject('nope')
.then(function () { console.log('never printed'); })
.fail(function (why) { console.log('rejection travelled down the chain:', why); });
console.log('typeof .pipe:', typeof $.Deferred().pipe, '| typeof .then:', typeof $.Deferred().then);
chained value: 7
rejection travelled down the chain: nope
typeof .pipe: function | typeof .then: function
waited for returned promise: after a delay
Since jQuery 1.8, .then() returns a new promise. Its value is whatever your callback returns. So you can transform a result (2 → 6 → 7 above). If the callback returns another promise, the chain waits for it. The second example paused for the 100 ms timer before the next step ran. A rejection skips the success steps and travels down to the first .fail(), as the third example shows. The older .pipe() method did this job before 1.8. It still exists in this version, as the last line shows, but it is deprecated. Use .then().
Sequential requests: the second needs the first
$.getJSON('/json')
.then(function (user) {
// use the first response to build the second request
return $.getJSON('/echo?user=' + user.id);
})
.then(function (echo) {
console.log('second request query:', echo.query);
}, function () { console.log('either request failed'); });
second request query: ?user=7
This is the cure for nested callbacks. The first request returns a user. The .then() callback uses the user’s id to build the second request and returns it. The next .then() receives the second response. The code reads top to bottom, and one failure handler at the end catches a failure in either step. The nested version of the same logic indents one level per request and repeats the error handling at each level.
Parallel requests: $.when()
var started = Date.now();
$.when(
$.getJSON('/slow?ms=300'),
$.getJSON('/json?x=1')
).done(function (a, b) {
// each argument is [data, textStatus, jqXHR]
console.log('a is array of', a.length, '->', JSON.stringify(a[0]), a[1]);
console.log('b[0].q ->', JSON.stringify(b[0].q));
var ms = Date.now() - started;
console.log('both finished in about', ms < 500 ? '300 ms (parallel)' : ms + ' ms');
});
// Plain values work too, and a single non-promise resolves right away
$.when('x', 5).done(function (a, b) { console.log('plain values ->', a, b); });
$.when().done(function () { console.log('$.when() with nothing resolves immediately'); });
plain values -> x 5
$.when() with nothing resolves immediately
a is array of 3 -> {"slow":true} success
b[0].q -> {"x":"1"}
both finished in about 300 ms (parallel)
$.when() takes several promises and returns one that resolves when all of them have. Both requests ran at the same time, so the total time matched the slowest one (300 ms), not the sum. Two details in the output matter. First, with several AJAX requests, each argument to .done() is an array of that request’s own arguments, [data, textStatus, jqXHR]. So a[0] is the data. With a single request, you get the three arguments directly. Second, $.when also accepts plain values, which resolve immediately. An empty $.when() resolves at once too. That helps when code only sometimes has work to wait on.
Failure is fail-fast
var t0 = Date.now();
$.when($.getJSON('/slow?ms=600'), $.getJSON('/missing'))
.done(function () { console.log('done'); })
.fail(function (jqXHR, textStatus, errorThrown) {
console.log('fail as soon as ONE rejects:', jqXHR.status, errorThrown, '| after', Date.now() - t0 < 400 ? '< 400 ms' : '>= 400 ms');
});
fail as soon as ONE rejects: 404 Not Found | after < 400 ms
If any promise you pass to $.when rejects, the combined promise rejects immediately. It does not wait for the others. Here the 404 arrived while the 600 ms request was still running, and the .fail() callback ran straight away. That is usually what you want. But the slower request is still in flight, and jQuery ignores its result. If you need every outcome, wrap each request so that it never rejects.
jQuery Deferred exceptions: the gotcha
var d = $.Deferred();
d.done(function () { throw new Error('bug in my handler'); });
d.fail(function () { console.log('fail handler ran'); });
try {
d.resolve();
console.log('resolve() returned normally');
} catch (e) {
console.log('resolve() threw:', e.message);
}
console.log('state:', d.state(), '(the Deferred still counts as resolved)');
// then() does not turn a thrown error into a rejection either
var d2 = $.Deferred();
var chained = d2.then(function () { throw new Error('inside then'); });
try { d2.resolve(); } catch (e) { console.log('then() callback threw out of resolve():', e.message); }
console.log('chained state:', chained.state());
resolve() threw: bug in my handler
state: resolved (the Deferred still counts as resolved)
then() callback threw out of resolve(): inside then
chained state: pending
This is the largest difference from Promises/A+, and the one most likely to bite in production. In a jQuery Deferred, an error thrown inside a .done() or .then() callback does not become a rejection. It escapes straight out of the code that called resolve(), which is often your AJAX completion handler. The Deferred still counts as resolved. In the .then() case, the derived promise stayed pending forever, so its .fail() handler never runs.
In practice this means two things. Wrap risky code inside callbacks in try/catch and decide what happens. And do not count on a final .fail() to catch your own bugs. If you need standard behaviour, libraries such as Q and when.js implement Promises/A+ and can wrap a jQuery promise.
Returning promises, not Deferreds
// Return only the read-only promise so callers cannot resolve or reject it
function wait(ms) {
var d = $.Deferred();
setTimeout(function () { d.resolve(ms); }, ms);
return d.promise();
}
var p = wait(50);
console.log('has done() :', typeof p.done);
console.log('has resolve() :', typeof p.resolve);
console.log('has state() :', typeof p.state);
p.done(function (ms) { console.log('waited', ms, 'ms'); });
has done() : function
has resolve() : undefined
has state() : function
waited 50 ms
A Deferred holds both sides of the contract. It has the producer methods (resolve, reject, notify) and the consumer methods (done, fail, then). Return d.promise() from your function, and callers receive only the consumer side. They cannot settle your Deferred by accident. The output shows it: the promise has done and state, but resolve is undefined. $.ajax() does the same, which is why you can never resolve a jqXHR by hand.
Animations are promises too
$.fx.off = true; // finish animations instantly so this runs without a layout engine
$('#box').fadeOut(400).promise().done(function () { console.log('fadeOut finished'); });
$('#box').slideDown(400).promise().done(function () { console.log('slideDown finished'); });
fadeOut finished
slideDown finished
Since jQuery 1.6, .promise() on a selection returns a promise that resolves when all animations on those elements finish. “Fade out, then load, then fade in” no longer needs nested animation callbacks. My test turns animations off with $.fx.off so they finish instantly. On a real page, the callbacks run after the 400 ms.
Troubleshooting promise chains
| Symptom | Cause | Fix |
|---|---|---|
Combined .done() receives odd arguments |
$.when wraps multi-argument results in arrays |
Read a[0], b[0] for the data |
.fail() runs before the slow request finishes |
$.when rejects as soon as one fails |
Expected; handle or ignore stragglers |
A bug in a callback never shows in .fail() |
Exceptions are not turned into rejections | try/catch inside callbacks; log errors |
| Chain continues after a failed step | A .fail() or error filter swallowed the rejection |
Return a rejected promise from the handler to propagate it |
| Callers resolve your Deferred by mistake | You returned the Deferred itself | Return d.promise() |
.pipe is deprecated |
Replaced in 1.8 | Use .then() |
| Callback ran earlier than expected | Callbacks run synchronously | Do not depend on later code having run |
How I use jQuery Deferred on real sites
- Parallel page data. A dashboard starts all its AJAX calls at once and uses
$.whento render when they are all back. - Cached promises. A function keeps the promise for an expensive lookup in a variable and returns it on every call. The request runs once, and later callers get the stored answer, even after it finished. I use this for the “load more” pattern in my post on how to load data without a page refresh.
- A shared “ready” promise. Page modules wait on one promise that resolves when configuration and templates have loaded. No module polls a flag.
- Cleanup in one place.
.always()hides the spinner and re-enables the button, whatever the outcome.
The jQuery Deferred bug that costs the most time is the chain that never settles. A typo inside a .then() callback throws. jQuery does not turn the error into a rejection, so the final .fail() never runs and the spinner stays on screen. The console shows the error, but far from the code that set up the chain. Now I keep callbacks small, wrap anything that parses or touches the DOM in try/catch, and reject on purpose when it fails.
Callback, Deferred or something else
- Is it one request with one outcome? A plain
.done()and.fail()on the request is enough. - Do several things run in parallel? Use
$.when. - Does one step need the previous result? Chain with
.then(), returning the next promise. - Are you writing a function that takes time? Return
d.promise()and callresolveorrejectexactly once. - Do you need standard promise semantics? Consider a Promises/A+ library such as Q and wrap jQuery’s promises.
When not to use a Deferred
- Something that happens many times. A click happens again and again, while a Deferred settles once. Use an event.
- One callback that is already simple. A single
successfunction does not need a promise around it. - When you need to cancel. You cannot cancel a Deferred. Keep the
jqXHRand call.abort()on it.
Common Deferred mistakes
- Returning the Deferred instead of its promise. Callers can then resolve it.
- Assuming a thrown error becomes a rejection. In this version it does not.
- Forgetting to return the inner promise inside
.then(). The chain does not wait. - Treating
$.whenarguments as plain data. Several requests give arrays. - Using
.pipe()in new code. It is deprecated. - Resolving twice. jQuery quietly ignores the second call.
FAQ
What is a jQuery Deferred object?
A jQuery Deferred is an object that stands for work that completes later. The code doing the work calls resolve or reject once. Other code attaches done, fail and always callbacks to hear the outcome. Every $.ajax() call returns one.
How do I wait for multiple AJAX requests in jQuery?
Pass them to $.when(req1, req2) and attach .done(function (a, b) { … }). The requests run in parallel. Each argument is an array of [data, textStatus, jqXHR] for that request. If one fails, the combined promise rejects immediately.
What is the difference between .then() and .done()?
.done() attaches a success callback and returns the same promise. .then(), from jQuery 1.8, returns a new promise whose value is what your callback returns. It waits if the callback returns a promise, so it is the tool for chaining steps.
Are jQuery promises the same as standard JavaScript promises?
No. jQuery’s Deferred predates the Promises/A+ standard and differs from it. Callbacks run synchronously, and an exception inside a callback does not become a rejection. Libraries such as Q and when.js implement the standard.
What is the difference between a Deferred and a promise in jQuery?
A Deferred has producer methods (resolve, reject, notify) and consumer methods (done, fail, then). A promise, from d.promise(), has only the consumer methods. That makes it safe to return to callers who should not settle it.
Deferreds do not make asynchronous code simpler. They make it honest. Every slow thing gets a name, an outcome and a place to handle failure. My rule of thumb for jQuery Deferred: return a promise from every function that takes time, and put a try/catch inside callbacks that can throw.
Originally posted on 15 October 2013 and post last modified on 2 November 2026