Himanshu SharmaSoftware engineer
Let’s talk

jQuery · Lesson 38 of 45

Deferreds, promises and async/await

Understand jQuery Deferreds and promises, chain requests with .then(), run requests in parallel with $.when(), and write Ajax code with async and await.

13 minAjaxUpdated 23 October 2026

By the end of this lessonSequence and combine asynchronous jQuery work cleanly, using .then(), $.when() or async/await, and handle errors in one place.

Promises in one paragraph

Ajax and animations finish later. A promise is an object that stands for that future result. You attach functions to it, and they run when the result is ready (the promise is resolved) or when it fails (rejected). jQuery's version is called a Deferred, and you have already used it: the jqXHR returned by $.ajax() is a promise, and so is .promise() on an animated selection.

Since jQuery 3, jQuery's promises follow the same standard as native JavaScript Promise, so they work with .then(), .catch() and await.

Running requests in sequence

Often one request depends on another: load a user, then load that user's posts. Nesting callbacks gets messy quickly. With .then(), return the next request and the chain waits for it:

html
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  const api = "https://jsonplaceholder.typicode.com";

  $.getJSON(api + "/users/4")
    .then(function (user) {
      console.log("1. user:", user.name);
      return $.getJSON(api + "/posts", { userId: user.id });
    })
    .then(function (posts) {
      console.log("2. posts:", posts.length);
      return $.getJSON(api + "/comments", { postId: posts[0].id });
    })
    .then(function (comments) {
      console.log("3. comments on the first post:", comments.length);
    })
    .catch(function (xhr) {
      console.log("something failed:", xhr.status);
    });
</script>
result
1. user: Patricia Lebsack
2. posts: 10
3. comments on the first post: 5

Each .then() receives the result of the previous step. One .catch() at the end handles a failure at any step, much tidier than an error handler in every nested callback.

Note one difference from .done(): when a then callback receives a jqXHR's result, it gets only the data as its first argument. In a jQuery .then() the status and jqXHR still arrive as extra arguments, but await and native promises only see the data.

Running requests in parallel

When requests do not depend on each other, start them together and wait for all of them with $.when():

html
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  const api = "https://jsonplaceholder.typicode.com";

  $.when(
    $.getJSON(api + "/users/1"),
    $.getJSON(api + "/todos", { userId: 1 })
  ).done(function (userResult, todosResult) {
    const user = userResult[0];       // each result is [data, textStatus, jqXHR]
    const todos = todosResult[0];
    const done = todos.filter(t => t.completed).length;
    console.log(user.name, "has completed", done, "of", todos.length, "to-dos");
  }).fail(function () {
    console.log("one of the requests failed");
  });
</script>
result
Leanne Graham has completed 11 of 20 to-dos

Both requests ran at the same time, so the total wait is the slowest request, not the sum. With several jqXHR arguments, each result passed to .done() is an array of [data, textStatus, jqXHR], hence the [0]. $.when() fails as soon as any request fails.

The native equivalent, Promise.all([...]), also works with jqXHR objects and gives you just the data: Promise.all([a, b]).then(([user, todos]) => ...).

async and await

Because jqXHR is a promise, you can use async functions and await, which make asynchronous code read like normal top-to-bottom code:

html
<p id="summary"></p>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  async function showSummary(userId) {
    try {
      const user = await $.getJSON("https://jsonplaceholder.typicode.com/users/" + userId);
      const albums = await $.getJSON("https://jsonplaceholder.typicode.com/albums", { userId });
      $("#summary").text(user.name + " has " + albums.length + " albums");
      console.log($("#summary").text());
    } catch (xhr) {
      $("#summary").text("Could not load user " + userId);
      console.log("caught a failure, status", xhr.status);
    }
  }

  showSummary(5).then(() => showSummary(12345));
</script>
result
Chelsey Dietrich has 10 albums
caught a failure, status 404
  • await pauses this function only until the promise settles, then gives you the data.
  • A failed request makes await throw, so try/catch handles errors, receiving the jqXHR.
  • The rest of the page keeps running while the function waits.

For new code, async/await is usually the clearest style.

Making your own Deferred

You can create promises for your own asynchronous work with $.Deferred(). Keep the Deferred private, and give callers its .promise(), which can be listened to but not resolved from outside:

html
<p>Delete this item?</p>
<button id="yes">Yes</button> <button id="no">No</button>

<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  function confirmDialog() {
    const deferred = $.Deferred();
    $("#yes").one("click", () => deferred.resolve("confirmed"));
    $("#no").one("click", () => deferred.reject("cancelled"));
    return deferred.promise();
  }

  confirmDialog()
    .then(answer => console.log("user", answer, "- deleting"))
    .catch(answer => console.log("user", answer));
</script>
result
user confirmed - deleting

The code that asks the question does not need to know about buttons; it just waits for the promise. In modern code you can do the same with new Promise((resolve, reject) => ...).

Errors inside then callbacks

If your own code throws inside a .then() callback, the chain is rejected and the error goes to the next .catch(), just as with native promises. jQuery also prints a warning in the console to help you find bugs. With .done() callbacks, by contrast, an error is thrown straight to the browser. Prefer .then() and .catch() in new code for predictable error handling.

The slim build

Remember that the jQuery slim build leaves out Deferreds along with Ajax and effects. If you use the slim build, use native Promise and fetch() instead.

Common mistakes

  • Forgetting to return the next request inside .then(), so the chain does not wait.
  • Starting dependent requests in parallel, before the data they need exists.
  • Reading $.when() results without [0] when several requests are combined.
  • Using await outside an async function (outside of modules, which allow top-level await).
  • Unhandled rejections: every chain needs a .catch() or try/catch.

Exercise

Write an async function that loads /posts/7 and then the author from /users/<post.userId>, and logs "<post title> by <author name>". Handle failures with try/catch.

Show one possible answer
html
<script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
<script>
  async function byline(postId) {
    try {
      const post = await $.getJSON("https://jsonplaceholder.typicode.com/posts/" + postId);
      const author = await $.getJSON("https://jsonplaceholder.typicode.com/users/" + post.userId);
      console.log(post.title + " by " + author.name);
    } catch (xhr) {
      console.log("failed:", xhr.status);
    }
  }
  byline(7);
</script>
result
magnam facilis autem by Leanne Graham

Key points

  • A promise represents a result that will arrive later; jqXHR and animation .promise() are both promises.
  • .then() returns a new promise, so returning a request from it runs requests one after another.
  • $.when(a, b) waits for several promises in parallel and fails as soon as one fails.
  • Because jqXHR is thenable, you can await it inside an async function and catch errors with try/catch.

Check yourself

1. What does returning a new $.ajax() from a .then() callback do?
2. What does $.when(requestA, requestB).done(fn) wait for?
3. Inside an async function, what does const user = await $.getJSON(url) give you?
4. How do you handle a failed request with await?