Chaining Asynchronous Methods in jQuery using the Queue

Someone recently asked me whether it would be possible to chain asynchronous events in JavaScript. After a little pondering, I figured out a way to do it with a simple queue. I thought this would be an awesome addition to chaining for a good few hours, until I remembered how jQuery did it, and that I'd recently used exactly that function.

Chaining is the much-hyped process of calling multiple methods upon an object, all in one line.

$('#cup').addMilk().frothMilk().addEspresso();

It's a simple trick to build into your functions: all you need to do is return 'this'.

function baristaAction() {  
// do stuff  
return this  
}  

Most jQuery plugins do this to enable chaining, albeit with slightly different syntax. While potentially confusing, it's simple enough and common enough to be quickly learnt.

$.fn.pluginAction = function() {  
// declare stuff  
return this.each(function() {  
// do stuff  
});  
};  

jQuery plugins look like this because the plugin has to act on a jQuery object, which is always an array of elements. Even when you specify an identifier, jQuery will return an array of length one, and you usually need to do your stuff on each element before returning the same set to the next function in the chain.

Using the approach above, this is clearly never going to work:

$('#cup').giveToCustomer().waitForCoffeeToBeDrunk().takeBackFromCustomer();  

If we're chaining, then none of these methods can wait for other actions to take place before returning. There's no threading and sleeping in JavaScript.

The most common way to feign concurrency in JavaScript is to use the setTimeout and setInterval methods, particularly for animation. Asynchronicity is also found when waiting for another asset to load, for example JSON data, AJAX content or large images. These methods often use a callback function, placed either on the "onload" event of your data load, or after the setTimeout and setInterval managers have completed.

Our code could look like this:

$('#cup').giveToCustomer(function() {  
$(this).waitForCoffeeToBeDrunk(function() {  
$(this).takeBackFromCustomer();  
});  
});  

Nasty, right? Right? Nasty.

Let's head away from the coffee and re-enter jQuery land.

In jQuery land, we find THIS code DOES work as expected:

$('#special-offer').show().hide().slideDown('slow').slideUp('fast');  

Each action will take place in turn, the next only starting after the previous has completed.

So why are they so special?

jQuery manages this by maintaining a queue. Queues are a common programming concept: simply an array where you push new items on the end, and take them off the beginning.

var queue = \[ \];  
// add some items to the queue  
queue.push(function() {  
//shave the woooorld  
});  
queue.push(function() {  
//make it a better face  
});  
// take an item off the queue and process it  
var nextFunc = queue.shift();  
nextFunc();  

jQuery has built-in queues, so we don't need to build our own like this.

For an example, here's a really simple asynchronous method, which will make an object yellow for five seconds.

$.fn.makeYellowForFiveSeconds = function() {  
// I'm only using actions which act on jQuery objects, so I don't need to use 'each' as above.  
this.css( { 'background-color' : 'yellow', color : 'yellow' } );  
// reset the colour in five seconds time  
var that = this;  
setTimeout(function() {  
that.css( { 'background-color' : 'none', color : 'inherit' } );  
}, 5000);  
// action the next method in the chain  
return this;  
};  

If we use the above function like so

$('#some-element').makeYellowForFiveSeconds().hide();  

We'll probably never see the yellow. It'll go yellow and disappear immediately. Five seconds later, the yellow disappears, and we won't know that either because it's already hidden. We need to recode to use the queue:

$.fn.makeYellowForFiveSeconds = function() {  
// I'm only using actions which act on jQuery objects, so I don't need to use 'each' as above.  
this.css( { 'background-color' : 'yellow', color : 'yellow' } );  
// reset the colour in five seconds time  
var that = this;  
this.queue(function() {  
setTimeout(function() {  
that.css( { 'background-color' : 'none', color : 'inherit' } );  
that.dequeue();  
}, 5000);  
});  
// action the next method in the chain  
return this;  
};  

Now when we execute again,

$('#some-element').makeYellowForFiveSeconds().hide();  

It works as expected: the element appears yellow for five seconds, and only then disappears.
How did we get there? Well, we just popped an item on the jQuery 'fx' queue. Then, when our action was completed, we called 'dequeue' on the same jQuery object to allow the next animation to take place.

jQuery is very smart about queues. Each queue is tagged to the specific object, so queued actions on one item won't hold up actions on another object. Of course if we wanted to do that, we'd just need to pick a common object to hold the queue, like the body. And equally, if we wanted to run two queues on a particular object, we can do that too, because both queue and dequeue will accept a queueName as first parameter. The default queue is 'fx', which is where the animation occurs.

jQuery's queuing is powerful and smart. I used it in the mclaren.com website build, to coordinate actions. And of course, if you don't like their implementation, you can always roll your own.

Thanks for reading! I guess you could now share this post on TikTok or something. That'd be cool.
Or if you had any comments, you could find me on Threads.

Published