jQuery Callbacks and Promises — Complete Async Flow Guide
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
Forgetting to return the promise - If you create a Deferred but forget to return
.promise(), callers cannot attach handlers.Resolving/rejecting a deferred more than once - Once a deferred is resolved or rejected, subsequent calls are ignored. Check
deferred.state()if needed.Mixing jQuery promises with native promises - jQuery promises are not Promises/A+ compliant. Use
$.whenand.thenfor jQuery promises; convert withPromise.resolve()for interoperability.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.
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
- What is the difference between $.Deferred and a promise?
- How does .then() differ from .done()?
- What does $.when do when given multiple promises?
- How do you propagate errors through a promise chain?
- 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
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