Here, the execution doesn’t wait for the timeout to complete but moves straight on (moving straight from “step 1” to “step 3”) and only prints out “step 2” once our outer code has relinquished control.
In real life situations, delays are not as predictable as a set timeout value (e.g. responses from web servers depend on several factors and successive calls may have difficult to predict delays). Let’s mimic that situation by creating a function that sets a random timeout:
Now we can see that successive runs of the script result in the steps being executed in an unpredictable order (varying for each run).
A Tangle of Callbacks
When I write code, I usually want it to run in the order that I specify. The usual way to do this is to set callbacks and use them to impose order. Here is an example of how we would use a set of callbacks to order three timeouts in a way Eddie Cochran would be happy with:
One common criticism of this approach is that it makes the execution of the code difficult to follow. This example is not too difficult to understand, but if we start adding in more callbacks with some parallel and some in series, it quickly becomes a tangle of callbacks.
One technique which can be used to handle asynchronous behaviour in a different way is the use of “promises”.
Suppose that a friend promises you 5 pounds (or 500 rupees). Although it is not actually money, you can treat it as money in certain situations. For example, suppose Alice promises you 5 pounds and you owe Bob 5 pounds. You could pass Alice’s promise of 5 pounds to Bob - when Alice gets the 5 pounds she gives it directly to Bob.
Alternatively, you can build a “chain” of promises. You could promise Bob that you will give him 5 pounds when Alice gives you 5 pounds (and Bob may then make a similar promise to Charlie, and so on).
You can also deal with groups of promises. Suppose that you need 15 pounds to buy something and you have a promise from each of Alice, Bob and Charlie of 5 pounds. When all those promises are fulfilled, then you can buy what you need.
You may also have an untrustworthy friend who fails to fulfill their promise - so you never get the 5 pounds.
Here is a simple example to show the inner mechanics:
We start by “requiring” the “when” module. Then we set up a promise using the when module’s “defer” function. Note that use of “defer” is not usually needed (and is considered an “anti-pattern” in most cases) but it is useful for our example here.
At this point we have a promise (like someone promising us 5 pounds) so we output it to the console to see what we have.
We then “resolve” the promise (like someone actually giving us the 5 pounds) and we output that to the console too.
Here is the output of this script:
You can see that the first time we display the promise, the state is “pending”, but after we have resolved the promise it now has a “fulfilled” state and the value is available. Although we have called the variable “promise”, you can also see from the above that the “promise” is actually part of that structure. We usually, therefore, refer to these variables as something like “deferred” and talk about the “promise” as the “promise” part of that structure. This is the convention we will use for the rest of this blog article.
Let’s go back to our “mockHttp” example and see how we would write that using promises:
We start our function by creating our “deferred”. We then set a timeout for a random period. When the timeout completes, the callback function resolves our “deferred” with the message passed in as a parameter to the mockHttp call. We return the “promise” part of our “deferred”. This is a common technique to keep any modifications to the promise within the function. Finally, we trigger all this by calling our “mockHttp” function and sending the output to the console. Let’s see what happens when we run this:
Perhaps surprisingly, we get the promise in its “pending” state. Why did this happen?
This is actually where the power of promises comes to our rescue. We can call a “then” function on any promise, which only gets executed when the promise is resolved. Here is the above example rewritten in that way:
The way this works is that the function passed as the parameter in the “then” call gets passed the value of the promise when it gets resolved.
One important point to note is that the “then” function also returns a promise. This allows us to create chains of promises, for example:
More Complex Promise Structures
Suppose we have an array of calls we want to make and iterate through each one:
Here we are outputting to the console when each step is setup and when each timeout is resolved. As with our examples at the start of this blog, randomness is evident when we run the script:
Three runs of the script and three different resolution orders (as expected).
What we will do to solve this problem is build a chain of promises. This will then mean that whatever order the resolution happens in, we will handle them in our desired order. Here is the new version of the iteration through the array:
On the first time through our iteration, the promise is “null” so we initialise it with our first “mockHttp” call. In subsequent iterations, we use the “then” method to build up our chain of promises - each one for the next step in the array.
This then imposes our required order on the function calls:
Sometimes, we don’t mind which order the promises are executed in so long as they are all complete before we take the next step. In such situations, we can use the “all” function:
We start off with the same set of steps we had before (and output them to the console). We then use the “map” function to replace each step with a call to “mockHttp”. Since “mockHttp” returns a promise, we now have an array of promises (which we output to the console). We can then use the “all” method from the “when” module to give us a promise which is only fulfilled when all the promises in the array are themselves fulfilled. Finally, we use the “then” function on that promise to output the steps array a final time. Running this script we get the following:
As you can see, we first have the array of the name of the steps. This then gets updated to be an array of promises (from “mockHttp”), each of which is pending. Finally, we can see the array of promises after the “all” promise has been fulfilled, where each of the promises in the array is now fulfilled and we can see the values they resolved to.
We therefore have two approaches to handling groups of promises - one where it is important that the order of the promises is preserved and one where it doesn’t matter about the order, only that the all promises are fulfilled before we move on.
Failure to Resolve Promises
It is sometimes possible that a promise does not get resolved. For now, we will just note that as well as “resolving” a promise, we can also “reject” a promise. We can handle a rejected promise in a “then” method (as a second parameter), but a more common approach is to use a “catch” at the end of a chain of “then” calls. We will come back to look at this in more detail in a later blog post.