The right way of caching AJAX requests with jQuery

AJAX is everywhere and jQuery offers a nice simple API to easily create AJAX requests, what not many people know though, is that it also provides ways to ease the burden of callback hell which accompanies asynchronous code! It does so by giving us promises.

What’s a promise? It’s just an object with a given task which makes a promise to us that it will eventually resolve that task, and tell us either it succeeded or failed. This might seem silly but it’s actually quite useful.

In this article I’ll show how to use promises to cache AJAX requests in a clean and elegant way.

First, let’s make a good ‘ol AJAX call using jQuery to retrieve information from a song.

// Load a song information
$.post('/songs', { id: 1 }, function(response) {
    console.log(response);
});

So far, so good, now imagine I make several calls to that AJAX function, it makes sense to create a more generic function to wrap my repeated functionality

function loadSong(songId, callback) {
    $.post('/songs', { id: songId }, callback);
}

Now I can call

loadSong(1, function (response) { /* do something */ }
loadSong(2, function (response) { /* do something */ }
loadSong(3 function (response) { /* do something */ }

But I have generated a callback along the way, let’s get rid of it, assuming we just want to show the contents of the song, we can make a function for that

function showSong(response) {
    /* Show the song in the DOM somehow */
}

I can now do

loadSong(1, showSong);
loadSong(2, showSong);

Now I notice that I send a request to get the song with id 1 several times, I’d like to cache the response so I only send it once, one solution would be

var cache = {};
function loadSong(id, callback) {
    if(!cache[id] !== null) {
        callback(cache[id]);
        return;
    } 

    if(cache[id] === null) {
        return;
    }

    cache[id] = null;
    $.post('/songs', { id: id }, function (data) {
        cache[id] = data;
        callback(cache[id]);
    });
}

Now we have a cache, initially I set the cache to null and don’t change it to an object until the AJAX success callback has been executed, if the cache is null I know I’m waiting for an AJAX call to resolve.

This solution works but it’s repetitive and quite ugly, there’s a better way, with promises! Turns out jQuery AJAX functions already return a promise, as stated before promises are just objects will eventually be resolved. How do we know if it’s resolved? We just pass a callback to the done function! For example

var deferred = $.post('/songs', { id: 1 });
deferred.done(callback);

Notice that I named the variable deferred, this is because it’s actually a deferred object which is a superset of promises, according to jQuery’s official docs

The Promise exposes only the Deferred methods needed to attach additional handlers or determine the state (then, done, fail, always, pipe, progress, and state), but not ones that change the state (resolve, reject, notify, resolveWith, rejectWith, and notifyWith).

var promise = $.post('/songs', { id: 1 }).promise();
promise.done(callback);

It’s safer to work with promises when we don’t need to update the state. A nice thing about promises is that if we call done once the promise has been resolved, the callback is called instantly! Knowins this we can rewrite our little cache as follows

var cache = {};
function loadSong(id, callback) {
    if(!cache[id]) {
        cache[id] = $.post('/songs', { 'id': id }).promise();
    } 
    cache[id].done(callback);
}

Now that’s much cleaner!

Bonus Example #

Another awesome way to use Deferred is to load chained AJAX request. Assume we want to load several songs at once, and we have to wait to get all songs before doing the rendering, with regular callbacks this is hell! You have to chain AJAX calls inside callbacks and such!

$.post('/song', { id: 1 }, function (song1) {
    $.post('/song', { id: 2 }, function (song2) {
        $.post('/song', { id: 3 }, function (song3) {
            drawToDOM(song1, song2, song3);
         } 
    });
});

EW! that codei s ugly, we could create functions for each callback but we’ll end up with a lot of spaghetti code anyways!

Deferred to the rescue! We can rewrite the code as follows:

$.deferred.when($.post('/song', { id: 1 }), $.post('/song', { id: 2 }, $.post('/song', { id: 3 }).then(drawToDOM);

function drawToDOM(song1, song2, song3) {
    /* draw :) */
}

As you can see the when function is itself a promise which resolves once all the promises inside it has been resolved, using it we solved our previous problem in a short, maintainable and elegant way!

Conclusion #

As you can see promises are an elegant way to solve the callback hell asynchronous code generates, and it’s really easy to use them! You should use them whenever you have the chance to avoid the issues exposed in this article.

For more information on deferreds I highly recommend watching this short video of jQuery conf 2012 which very nicely explains a lot about them. Also don’t forget to check out jQuery official documentation. Cheers!

 
1,259
Kudos
 
1,259
Kudos

Now read this

Object Oriented Javascript

If you are reading this I’d like to assume you know what Javascript is and the fact that it’s object oriented, what not many people know though is how to do the most common object oriented “things” in Javascript, such as inheritance,... Continue →