Just jQuery The Core UI - Function Queues
Written by Ian Elliot   
Saturday, 21 January 2023
Article Index
Just jQuery The Core UI - Function Queues
The Async Problem
The Next Function
Listing

smallcoverjQuery

The Next Function

The need to preserve this to pass to any asynchronous portion of the function is a complication. Since jQuery 1.4 the function queue has passed the next function in the queue as the first argument to the function currently being dequeued. This makes it possible to call the next function and dequeue it without having to keep a reference to the object that the queue is hosted by. 

For example we could write any of the functions given earlier as:

function myF1(next) { 
 setTimeout(function () {
   console.log("myF1");
   next();
 }, 3000);
}

The call to next dequeues the next function and executes it. Notice that you don't need to pass next to the asynchronous function because it is accessible as part of a closure.

Program Listing

The complete listing of the program so far including the use of the next parameter is:

var obj = $({}).queue("myQueue", myF1)
                .queue("myQueue", myF2)
                 .queue("myQueue", myF3);
obj.dequeue("myQueue"); 
function myF1(next) {   setTimeout(function () {    console.log("myF1");    next();  }, 3000); }
function myF2(next) {   setTimeout(function () {    console.log("myF2");    next();  }, 2000);
}
function myF3(next) {   setTimeout(function () {    console.log("myF3");    next();  }, 1000); }

Adding Functions to jQuery

Functions that work with queues are nearly always easier to use if they are added to jQuery. The reason is that you can then use them in a fluent or chained style which fits in with the way queues work. This is how the animation functions, see the next chapter, work.

The topic of adding functions to jQuery is covered in more detail in Chapter 15.

All you have to do to add a function to jQuery is use the fn.extend function:

$.fn.extend(object);

This works much like the extend function but it adds all of the properties of the object to the jQuery prototype. Notice that this means that every jQuery object acquires the new properties including any you might have already created. 

To add functions to jQuery, all you have to do is add properties that are functions to the object. If you want to use function chaining, which is the usual style of using functions in jQuery, then you need to make sure that the functions you define return this. The reason is that this is set to the call context, i.e. the current jQuery object, and it needs to be what the next function in the chain uses as its call context. 

So, to add our example functions to jQuery and allow them to be called in a chained style we need:

$.fn.extend({
 myF1: function () {
         setTimeout(function () {
            console.log("myF1"); 
         }, 3000);
         return this;
       },
 myF2: function () {
         setTimeout(function () {
            console.log("myF2");
         }, 2000);
         return this;
       },
 myF3: function () {
         setTimeout(function () {
            console.log("myF3");
         }, 1000); 
         return this;
       } });

After this we can call the three functions using:

$({}).myF1().myF2().myF3();

Notice that our three functions don't yet make use of a function queue. They are just simple functions that each adds its asynchronous part, i.e. the call to setTimeout, to the JavaScript event queue. The three calls to setTimeout then sit in the event queue until the specified time is up when they print their message to the console log.

This means that, in the log, we see myF3, myF2 and myF1, which might not be what we wanted. 

If we want the asynchronous chained functions to execute in the order written we need to use a queue.

Chained Auto Queuing

Manually queuing and dequeuing isn't difficult but it is a bit messy. Using the jQuery idiom of function chaining we can hide the details and still make everything work. This is how the animation functions and the default fx queue work. 

What we have to do to convert the three functions so that they automatically use a function queue is change them so that they add the asynchronous part to the queue and remember to call next to move the queue on. This part is fairly easy.

The difficult part is that we need an extra dequeue command to get things moving. It is difficult because you only want to do this when the first function is added to the queue. After that you can rely on the functions themselves to call next to move the queue on. Of course if the functions all finish then the queue becomes empty and the next time a function is added you once again need the extra dequeue command. 

The default queue keeps track of when the queue is active by placing a string at the start of the queue to act as a signal. We can't make use of this exact method for a general queue without modifying the inner workings of jQuery. Instead we can add a null function to the end of the queue to act as a state indicator.

The idea is that when you are adding a function to an empty queue you know that it isn't active – i.e. there are no functions still active that might call next to move the queue on. This works as long as you follow the rule that every function you put on the queue calls next. If you put n functions on the queue then you need an explicit dequeue for the first but the subsequent functions are all dequeued by the previous function calling next. The final function will dequeue the null function leaving the queue empty and this is the signal that the queue is inactive.

The first thing we have to do is test to see if the queue is active. To do this we simply pop the last item off the queue. If the queue is empty then there is no last item and state is undefined. If the queue is active then there is a null function at the end and it needs to be removed to add the new function:

myF1: function () { 
 var state = this.queue("myQ").pop();

With the null function removed we can add the function we actually want to place on the queue: 

this.queue("myQ", function (next) {
      setTimeout(function () { 
        console.log("myF1");
        next();        }, 3000); });

This is just the usual print a message to the console function complete with a call to next to move the queue on. 

Now we have to add the null function to the end of the queue:

this.queue("myQ", function () {});

This marks the queue as active but if it was inactive when we started adding a function to it we need to dequeue the function to get things going:

if (state === undefined)  this.dequeue("myQ");

Finally we return this so that the functions can be chained: 

 return this;
}

Making the same modification to each of the other functions means we can now call them using:

$({}).myF1().myF2().myF3();

and you will see myF1 printed in the log after 3 seconds followed by myF2 and myF3 after a further 2 and 1 seconds respectively. That is, the chaining automatically implements the queue to keep the asynchronous functions in the correct order. 

This might seem complicated but the only complication is due to the need to get the queue started with an extra dequeue operation. If you want to keep things simple you could manually include a dequeue.



Last Updated ( Saturday, 21 January 2023 )