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

The Asynchronous Problem

There are lots of examples of asynchronous functions in JavaScript. The best known is probably the Ajax call, but downloading almost any file is generally executed in an asynchronous fashion. For simplicity we are going to use the setTimeout function to simulate an asynchronous function.

To remind you:

setTimeout(function,milliseconds);

will run the specified function after a delay of the specified number of milliseconds. This really does run the function asynchronously because it adds a message to the event queue to run the function after the specified time and returns at once. It is also the basic way to create an asynchronous function that does some useful work while allowing the UI to run. 

To convert the example functions to asynchronous operation we simply move the log output into a separate function that runs after a few seconds:

function myF1() { 
 setTimeout(function(){console.log("myF1");},3000); 
 $(this).dequeue("myQueue");
}
function myF2() {
 setTimeout(function(){console.log("myF2");},2000);
 $(this).dequeue("myQueue");
}
function myF3() { 
 setTimeout(function(){console.log("myF3");},1000);
 $(this).dequeue("myQueue");
}

Now we have myF1 which prints myF1 on the log after waiting 3 seconds, myF2 which prints myF2 on the log after waiting 2 seconds and myF3 which prints myF3 onto the log after waiting 1 second. 

If these functions are queued in the usual way and dequeued then what happens might surprise you, but it shouldn't.

When myF1 is dequeued it sets an asynchronous call going which concludes after 3 seconds but it returns at once, i.e. before the asynchronous function has completed. Then the dequeue causes myF2 to execute and it adds an asynchronous function that executes after 2 seconds and immediately dequeues the next function, i.e. myF3, which adds an asynchronous function which executes after 1 second.

The result is that the queue adds each of the asynchronous functions almost at the same time and hence myF3 is first to print on the log, then myF2 and finally myF1. 

Notice that you get the same result if you simply call each of the functions in turn. That is, with:

myF1();
myF2();
myF3();

each function returns at once after placing an asynchronous function on the event queue. The result is the same and myF3 is printed to the log first, followed by myF2 and myF1. However, if the intent was to execute myF2 after myF1 has completely finished, including any asynchronous portions, and myF3 after myF2 has completely finished, then this is not what is happening. 

The simple solution is to remember that the dequeue function can be thought of as a signal to the rest of the program that the function has finished.

Currently the dequeue function is at the end of each of the functions but this does not mark the end of what the function does – clearly the call to dequeue should be at the end of the asynchronous part of the function. However, you can't just move the call to the asynchronous function as in:

function myF1() {
  setTimeout(function () {
    console.log("myF1");
    $(this).dequeue("myQueue");
  }, 3000); }

The reason this doesn't work is that by the time the asynchronous function is called this no longer references the object that hosts the queue. To get round this the simplest solution is to save this in another variable and then rely on closure to give the asynchronous function access to it. 

 

That is if you change the three functions to:

function myF1() { 
 var that = this;
 setTimeout(function () {
   console.log("myF1");
   $(that).dequeue("myQueue");
  }, 3000); 
}
function myF2() {   var that = this;  setTimeout(function () {    console.log("myF2");    $(that).dequeue("myQueue");  }, 3000);  }
function myF3() {   var that = this;  setTimeout(function () {    console.log("myF3");    $(that).dequeue("myQueue");   }, 3000);  }

Everything works as you would expect. Now when you dequeue for the first time the myF1 function is executed but its dequeue isn't called until 3000 seconds are up and after myF1 has been printed to the log. Thus, myF2 runs after myF1 is completely finished and it only calls dequeue after it has completely finished.

What this means is that after 3 seconds myF1 is printed, then after a further 2 seconds myF2 is printed and finally after a further 1 second myF3 is printed. 

You can see how by placing the dequeue instructions to indicate that the function has completely finished, including any asynchronous portion, you can ensure that the functions in the queue are executed one after the other. 

Of course you can implement modified versions of this scheme that might allow some asynchronous functions to overlap slightly or even completely. You just have to keep in mind that when you call the dequeue function the next function in the queue gets to start. 

This is what function queues are all about and there is nothing more to learn apart from some small extras that can make things easier. 



Last Updated ( Saturday, 21 January 2023 )