Best Practices
Issue using the standard way
Excessive nesting. Functions and events dependent on the returned AJAX data has to be wrapped in the success handler, because AJAX is by nature, asynchronous. Along the same line, this reduces the readability of your code.
Difficulty in chaining and compounding. It is difficult and overwhelmingly complex to evaluate outcomes of multiple AJAX requests — we are unable to predict how long does it take for all AJAX requests (each running asynchronously) to return a response.
Thankfully, promises and deferred objects are implemented natively in the $.ajax() method:
.done() as a replacement for .success()
.fail() as a replacement for .error()
.always() as a replacement of .complete()
jqXHR.done(function( data, textStatus, jqXHR ) {});
An alternative construct to the success callback option, refer to deferred.done() for implementation details.
jqXHR.fail(function( jqXHR, textStatus, errorThrown ) {});
An alternative construct to the error callback option, the .fail() method replaces the deprecated .error() method. Refer to deferred.fail() for implementation details.
jqXHR.always(function( data|jqXHR, textStatus, jqXHR|errorThrown ) { }); (added in jQuery 1.6)
An alternative construct to the complete callback option, the .always() method replaces the deprecated .complete() method.
In response to a successful request, the function's arguments are the same as those of .done(): data, textStatus, and the jqXHR object. For failed requests the arguments are the same as those of .fail(): the jqXHR object, textStatus, and errorThrown. Refer to deferred.always() for implementation details.
jqXHR.then(function( data, textStatus, jqXHR ) {}, function( jqXHR, textStatus, errorThrown ) {});
Incorporates the functionality of the .done() and .fail() methods, allowing (as of jQuery 1.8) the underlying Promise to be manipulated. Refer to deferred.then() for implementation details.
The code can be restructured as
$.ajax({
data: someData,
dataType: 'json',
url: '/path/to/script'
}).done(function(data) {
// If successful
console.log(data);
}).fail(function(jqXHR, textStatus, errorThrown) {
// If fail
console.log(textStatus + ': ' + errorThrown);
});
var ajaxCall = $.ajax({
context: $(element),
data: someData,
dataType: 'json',
url: '/path/to/script'
});
ajaxCall.done(function(data) {
console.log(data);
});
See that how the .done() callback is on the same level as the AJAX call itself, without requiring complicated nesting within the call per set? While this advantage may seem trivial on a cursory glance, it greatly improves the readability of your code, especially in a production environment where your code is likely to be cluttered with multiple asynchronous calls.
Multiple AJAX calls
Compounding several AJAX calls has never been made easier with promises. All you have to do is to listen to their status via $.when().
var a1 = $.ajax({...}),
a2 = $.ajax({...});
$.when(a1, a2).done(function(r1, r2) {
// Each returned resolve has the following structure:
// [data, textStatus, jqXHR]
// e.g. To access returned data, access the array at index 0
console.log(r1[0]);
console.log(r2[0]);
});
Dependence chain of AJAX requests
You can also chain multiple AJAX request — for example, when the second AJAX call relies on returned data on the first call. Let’s say the first call retrieves the session ID of a user, and we need to pass that value off to a second script. Remember that $.then() returns a new promise, which can be subsequently passed to the $.done() or even another $.then() method.
var a1 = $.ajax({
url: '/path/to/file',
dataType: 'json'
}),
a2 = a1.then(function(data) {
// .then() returns a new promise
return $.ajax({
url: '/path/to/another/file',
dataType: 'json',
data: data.sessionID
});
});
a2.done(function(data) {
console.log(data);
});
Modularizing AJAX requests
More often than not, one might want to modularize their code and delegate a single function to make dynamic AJAX requests. How should we invoke the AJAX call individually, and access the promise returned, in this case? It turns out to be beyond simple: simply return the AJAX object after you make a request.
// Generic function to make an AJAX call
var fetchData = function(query, dataURL) {
// Return the $.ajax promise
return $.ajax({
data: query,
dataType: 'json',
url: dataURL
});
}
// Make AJAX calls
// 1. Get customer order
// 2 Get customer ID
var getOrder = fetchData(
{
'hash': '2528ce2ed5ff3891c71a07448a3003e5',
'email': '[email protected]'
}, '/path/to/url/1'),
getCustomerID = fetchData(
{
'email': '[email protected]'
}, '/path/to/url/2');
// Use $.when to check if both AJAX calls are successful
$.when(getOrder, getCustomerID).then(function(order, customer) {
console.log(order.data);
console.log(customer.data);
});
Handling arrays of returned deferred objects
Sometimes you would want to use $.when() on an array of deferred objects. An example situation would be: making a series of AJAX calls (number of calls dynamically changes, perhaps?), and then checking when all of them are done. How does that work?
There are two options:
The easier to understand method would be to construct an empty array, use $.each() to make AJAX calls iteratively and then push the returned promise into the array.
The preferred method (personally) would be to use .map() to construct an object containing returned promises, which we then use .get() to return an array
After that it’s all easy: simply use $.when.apply($, array) to evaluate all AJAX calls performed:
// Let's say we have a click handler and fires off a series of AJAX request
$selector.click(function() {
// Construct empty array
var deferreds = [];
// Loop using .each
$(this).find('div').each(function() {
var ajax = $.ajax({
url: $(this).data('ajax-url'),
method: 'get'
});
// Push promise to 'deferreds' array
deferreds.push(ajax);
});
// Use .apply onto array from deferreds
$.when.apply($, deferreds).then(function() {
// Things to do when all is done
});
});
// Let's say we have a click handler and fires off a series of AJAX request
$selector.click(function() {
// Map returned deferred objects
var deferreds = $(this).find('div').map(function() {
var ajax = $.ajax({
url: $(this).data('ajax-url'),
method: 'get'
});
return ajax;
});
// Use .apply onto array from deferreds
// Remember to use .get()
$.when.apply($, deferreds.get()).then(function() {
// Things to do when all is done
});
});
Caching Ajax Call
// original code
$(function() {
function getPost(postId, callback) {
$.getJSON('/content-service/post/' + postId, callback);
}
getSomething(3, function(resp) {
console.log(resp);
});
});
// code with caching
$(function() {
var cache = {};
function getPost(postId, callback) {
var url = '/content-service/post/' + postId;
if (cache[url]) {
callback(cache[url]);
return;
} else {
$.ajax({
url : '/content-service/post/' + postId,
type : 'GET',
dataType : 'json',
success : function(resp) {
cache[url] = resp;
callback(resp);
}
});
}
}
getPost(3, function(resp) {
console.log(resp);
});
});