Asynchronous Code In JavaScript
Written by Ian Elliot   
Monday, 15 May 2017
Article Index
Asynchronous Code In JavaScript
Asynchronous Flow Of Control and Closure
Custom Asynchronous Functions
Summary

Asynchronous Flow Of Control and Closure

The effect that asynchronous code has on the structure of your program is easy to understand in terms of what code goes before and what code goes after:

simple

The statements that are before the asynchronous operation provide the context for the call. Variables which are set in this before code may contain values that are important for what to do with the result of the asynchronous operation in the code to follow it. The code that follows it makes use of the result of the operation and the context established by the earlier code. 

When you use a callback the code that follows the call to the asynchronous function doesn't follow - it becomes another function.

That is the "after" code - becomes the callback:callback

You can now see that the flow of control has been distorted - what was one function is now two. Don't worry too much about it at the moment but this is a complete picture of what can happen because if the Callback contains an asynchronous call you repeat the procedure of moving the "after" code into a callback - distorting the flow of control again. 

Not only that but in a simple programming environment the context is lost. As the "after" code is now a separate function it no longer has access to the variables contained in the "before" code. In short, the callback can't perform an instruction like Text(1,i) because i isn't only out of scope it doesn't even exist. 

Except of course in JavaScript it does.

JavaScript supports closure and this means that the variables that were in scope when a function was created remain in scope for it even if from the point of view of other parts of the program they have long been destroyed.  That means the callback does have access to the variable i and can perform result=Text(1,i) because closure provides all of the variables that were defined when it was. 

There are many complex and esoteric explanations of what closure is and why you might want it, but it is this automatic provision of context to a callback function that seems the most convincing. There are lots of other uses of closure, but it is this one that you would invent closure for. 

Closures ensure that callbacks have their context.

Of course things can be more complicated. It could be that the asynchronous function call is itself nested within a control structure that spans the before and after block of code.  

For example, if a callback is in a for loop then the loop executes and completes, possibly before any of the callbacks are activated. Similarly, an if statement cannot test the result of a callback because this isn't available at the time the code is executed. Closures solve many of the problems of callbacks, but not all. 

Asynchronous Errors

It is clear that using callbacks makes a mess of the flow of control of a program and it is difficult to ensure that everything happens in the correct order. However, often the order in which callbacks occur isn't important. For example, if you are loading a set of files it usually doesn't matter what order they are loaded in as longs they do load and are processed. A more common problem, and one that is often ignored, is what should happen when an error occurs. It has to be admitted that this is often a problem in synchronous code as well. 

The big difference in asynchronous code is that the error problem can occur at any time after the callbacks have been set and in any order. In a synchronous blocking program you might write:

try{
 load A
 load B
 load C
}
catch(e){
 deal with a problem
}

If you change the load functions to asynchronous functions then the catch doesn't catch any of the errors that the loads might produce and certainly not any that the callbacks might create. The reason is simply that the catch clause is executed before any of this code actually runs and it is well in the past when any errors might actually occur.

Handling errors is a big problem for asynchronous programs because it is difficult to know the state of the system when the error occurs. If file B fails to download what to do might depend on whether file A or C downloaded or not. The simplest way of dealing with errors is to provide an alternative callback that is used if the asynchronous function fails. For example:

load(A,success,error)

where success is called if the load works and error is called otherwise.

Error handling is one of the big advantages of using Promises to wrap callbacks - more of this in chapters 4 and 5.

The Callbacks List

There is an internal facility that jQuery uses to implement various callback related features. The Callbacks object can be used to keep a list of callbacks and trigger them all with a single command. This isn't often useful but if you plan to implement callback features in your own components then it can simplify things. 

The function:

var callback=$.CallBacks(options);

returns a callbacks list object. Once you have a callbacks list object you can use the add method to add functions and the remove method to remove functions from the list. To execute the callback list  all you have to do is use the fire method. There is also the disable method stops the callbacks being fired while keeping the list intact. If you use an array of function then you can add and remove a set of callbacks in one go. 

The options are a space separated list of strings consisting of: 

  • once: Ensures the callback list can only be fired once 

  • memory: Keeps track of previous values and will call any callback added after the list has been fired as it is added

  • unique: Ensures a callback can only be added once 

  • stopOnFalse: Interrupts callings when a callback returns false.

Using these and some other functions you can equip an object with a callback list and control exactly how it can be fired. 

The Function Queue - Sequential Async

Surprisingly one of the most commonly encountered problems is how to make async functions run in a particular order. If you make three async calls:

functionA();
functionB();
functionC();

then as already explained the problem is that A returns before it is complete and B is started almost at once followed by C. You can't say what order they will complete in and effectively they are running at the same time. 

If you want the advantage of an async function, i.e. keeping the UI responsive, but still want the functions to execute in a fixed order you have a problem. If each of the functions accepts a callback that is triggered when it finishes then you can write a sequential flow of control as:

functionA(functionB(functionC));

but this is often not possible and when it is the resulting code looks confusing. 

A better solution is to use a function queue and jQuery provides this facility because it needs it to choreograph its animation functions which are all asynchronous. The function queue was described in detail and with respect to its use in animation in Just jQuery: The Core UI but as it is a key idea in asynchronous functions it is covered here more quickly and more generally. 

Every element can have any number of function queues associated with it. Essentially a function queue is just an array used to store functions. To create a queue you can make use of the .queue function. 

For example, to add a function to the function queue of all divs you would use something like:

$("div").queue("myQueue",myFunction);

You can also setup a queue on an empty object if you don't want to associate it with a particular element or object:

var obj=$({}).queue("myQueue",myF1)
              .queue("myQueue",myF2)
               .queue("myQueue",myF3)

After this the queue has three functions stored in it. 

There are some functions to access and manipulate the queue: 

.queue("queueName") returns the queue as an array
.queue("queueName",newQueue)  replaces the queue with the array of functions specified by newQueue
.clearQueue("queueName") removes all the functions in the queue.

 

The whole point of a function queue is to dequeue functions from it when you need to execute them. 

For example, with the three-function queue associated with obj in the previous example:

obj.dequeue("myQueue");

will result in myF1 being executed. That is all that happens - dequeue runs a single function. If you want to execute the next function you need to use dequeue again:

obj.dequeue("myQueue");

runs myF2.

This doesn't seem particularly useful until you realize that you can put the dequeue in the functions that are in the queue. In this way you can build up a queue and included in each of the functions a dequeue command when it is time for the next function to run. Notice this works for async and synchronous functions. It is such a common way to use a queue that 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.

What this means is that any function in the queue can start the next function by simply using

next();

where next is the first argument passed to the function. 



Last Updated ( Thursday, 05 May 2022 )