Skip to content

jQuery Callbacks and Promises — Complete Async Flow Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about jquery callbacks and promises. We cover key concepts, practical examples, and best practices to help you master this topic.

jQuery callbacks and promises provide a structured way to manage asynchronous operations, coordinating multiple AJAX calls, animations, and timeouts with clean error handling and chaining.

What You'll Learn

  • Using $.Deferred for custom async operations
  • Promise methods: .done(), .fail(), .always()
  • Chaining promises and coordinating multiple requests
  • Converting callbacks to promises
  • Error handling and rejection patterns

Why It Matters

Nested callbacks create spaghetti code that is hard to read and debug. Promises flatten async flows into linear chains, making code easier to reason about and errors simpler to handle.

Real-World Use

A dashboard that loads user data, then their orders, then order details — each step depending on the previous. With promises, this chain is a clean .then().then().then() instead of nested callbacks.

Promise Flow

flowchart TD
    A[$.Deferred] --> B{Pending}
    B -->|resolve| C[Resolved]
    B -->|reject| D[Rejected]
    C --> E[.done() callbacks]
    C --> F[.then() success]
    D --> G[.fail() callbacks]
    D --> H[.then() error]
    E --> I[.always() runs]
    G --> I
    style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px

Creating a Deferred Object

function delay(ms) {
  var deferred = $.Deferred();

  setTimeout(function() {
    deferred.resolve('Done after ' + ms + 'ms');
  }, ms);

  return deferred.promise();
}

// Usage
delay(1000).done(function(message) {
  console.log(message); // Output: Done after 1000ms
});

Expected output: After 1 second, the promise resolves with the message "Done after 1000ms".

Basic Promise Methods

var deferred = $.Deferred();
var promise = deferred.promise();

// Attach callbacks before resolution
promise.done(function(data) {
  console.log('Success:', data);
});
promise.fail(function(err) {
  console.log('Error:', err);
});
promise.always(function() {
  console.log('Always runs');
});

// Resolve or reject
deferred.resolve('Operation completed');
// Output: Success: Operation completed
// Output: Always runs

// Alternative: callbacks on the deferred itself
deferred.resolve('Data');
deferred.done(function(data) { console.log(data); });

Converting AJAX to Promises

jQuery AJAX already returns a promise-compatible object:

var request = $.ajax({
  url: '/api/users',
  method: 'GET',
  dataType: 'json'
});

request
  .done(function(data) {
    console.log('Users:', data);
  })
  .fail(function(jqXHR, textStatus, errorThrown) {
    console.error('Request failed:', textStatus, errorThrown);
  })
  .always(function() {
    console.log('Request completed');
  });

Chaining Promises with .then()

$.ajax('/api/user/1')
  .then(function(user) {
    console.log('Got user:', user.name);
    return $.ajax('/api/orders?userId=' + user.id);
  })
  .then(function(orders) {
    console.log('Got orders:', orders.length);
    return $.ajax('/api/order/' + orders[0].id);
  })
  .then(function(orderDetail) {
    console.log('Order detail:', orderDetail);
  })
  .fail(function(err) {
    console.error('Any step failed:', err);
  });

Expected output: Each request waits for the previous one. If any step fails, the chain skips to the .fail() handler.

Coordinating Multiple Promises with $.when

var usersPromise = $.ajax('/api/users');
var configPromise = $.ajax('/api/config');
var translationsPromise = $.ajax('/api/i18n/en');

$.when(usersPromise, configPromise, translationsPromise)
  .done(function(users, config, translations) {
    console.log('Users:', users[0]);       // First argument is data
    console.log('Config:', config[0]);
    console.log('Translations:', translations[0]);
    // Initialize app with all data ready
  })
  .fail(function(err) {
    console.error('Failed to load required data');
  });

Expected output: The callback fires only after ALL three requests complete successfully. If any one fails, the .fail() handler runs.

$.when with Single Value

// $.when on a single promise
$.when($.ajax('/api/data'))
  .done(function(data) {
    console.log('Data:', data);
  });

// $.when with non-promise values (treated as resolved)
$.when('ready', 42, true)
  .done(function(v1, v2, v3) {
    console.log(v1, v2, v3); // Output: ready 42 true
  });

Promise Pipes (jQuery 1.8+)

Use .pipe() to filter or transform resolved values:

function getUser(id) {
  return $.ajax('/api/user/' + id);
}

function formatUser(user) {
  return {
    fullName: user.firstName + ' ' + user.lastName,
    email: user.email.toUpperCase()
  };
}

getUser(1)
  .pipe(function(user) {
    return formatUser(user);
  })
  .done(function(formatted) {
    console.log(formatted.fullName, formatted.email);
  });

Progress Notifications

Deferred objects support progress updates:

function uploadFile(file) {
  var deferred = $.Deferred();

  // Simulate progress
  var progress = 0;
  var interval = setInterval(function() {
    progress += 10;
    deferred.notify(progress);

    if (progress >= 100) {
      clearInterval(interval);
      deferred.resolve('Upload complete');
    }
  }, 200);

  return deferred.promise();
}

uploadFile('photo.jpg')
  .progress(function(percent) {
    console.log(percent + '% uploaded');
  })
  .done(function(message) {
    console.log(message);
  });

// Output: 10% uploaded, 20% uploaded, ... 100% uploaded, Upload complete

Creating Reusable Async Functions

function loadAppData() {
  return $.Deferred(function(deferred) {
    var resources = {
      template: '/templates/app.html',
      data: '/api/data',
      config: '/api/config'
    };

    function loadResource(name, url) {
      return $.get(url).done(function(content) {
        console.log('Loaded: ' + name);
      });
    }

    var requests = Object.keys(resources).map(function(key) {
      return loadResource(key, resources[key]);
    });

    $.when.apply($, requests)
      .done(function() {
        deferred.resolve('All resources loaded');
      })
      .fail(function() {
        deferred.reject('Failed to load resources');
      });
  }).promise();
}

loadAppData()
  .done(function(msg) { console.log(msg); })
  .fail(function(err) { console.error(err); });

Common Mistakes

  1. Forgetting to return the promise - If you create a Deferred but forget to return .promise(), callers cannot attach handlers.

  2. Resolving/rejecting a deferred more than once - Once a deferred is resolved or rejected, subsequent calls are ignored. Check deferred.state() if needed.

  3. Mixing jQuery promises with native promises - jQuery promises are not Promises/A+ compliant. Use $.when and .then for jQuery promises; convert with Promise.resolve() for interoperability.

  4. Not handling errors in AJAX chains - If any .then() handler throws an error, the chain rejects. Always add a .fail() at the end of chains.

  5. Calling .done() after resolution - Attaching .done() to an already-resolved promise still fires the callback. This is by design but can cause unexpected behavior if you expect one-time execution.

Practice Questions

  1. What is the difference between $.Deferred and a promise?
  2. How does .then() differ from .done()?
  3. What does $.when do when given multiple promises?
  4. How do you propagate errors through a promise chain?
  5. What is the purpose of the .notify() method on a Deferred?

Challenge: Build a resource loader that loads a template, data, and configuration in parallel. If any resource fails, the entire load fails. Show progress as each resource completes.

FAQ

Are jQuery promises compatible with ES6 Promises?

Not fully. jQuery promises do not implement the Promises/A+ spec. Convert with Promise.resolve($.ajax(url)) when mixing with native promises.

What is the difference between .done() and .then()?

.then() returns a new promise for chaining. .done() returns the same promise and is used for side effects. Prefer .then() for transformations.

Can I cancel a promise?

jQuery does not have built-in cancellation. Store a cancel flag and check it in your async operation. With AJAX, call .abort() on the jqXHR object.

How do I convert a callback-based API to a promise?

Wrap it in a $.Deferred: create the deferred, call the callback API, and resolve/reject the deferred in the callback.

What happens if I attach callbacks after resolution?

The callback fires immediately (synchronously) if the promise is already resolved. This is useful for caching results.

Mini Project

Build a data loading service that fetches user profiles and their posts in parallel, then renders them together. If the user is not found, reject the promise and show an error message. Log loading progress via progress notifications.

What's Next

Async operations need DOM updates. Learn how jQuery DOM manipulation integrates with your promise-based data loading patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro