Code

Asynchronous JavaScript

Synchronous vs. Asynchronous Code

By default, code in JavaScript executes synchronously, meaning one line after the next. This means that a line of code can’t execute until all of the previous lines of code in its lexical scope have finished executing.

This isn’t always desirable. Suppose, for example, our line of code is waiting for a server to send some data. It would be nice if we could take care of other things while we were waiting.

This is what asynchronous (async for short) code does. Instead of requiring the code following it to wait until it’s done executing, as synchronous code does, async code starts, returns control back to its calling context, and then notifies that calling context when it’s finished executing.

An analogy for synchronous execution might be a phone call. Suppose I want to know whether a package has made it to the post office. I call the post office. The clerk puts me on hold, goes to check whether the package is there, and comes back to me with an answer. I’m stuck on the phone until the clerk is done checking. That’s synchronous behavior.

On the other hand, I might also call the post office looking for my package and the clerk might say “I’ll need a few minutes to look that up for you. Perhaps I can call you back when I have an answer?” I say great, here’s my phone number. I hang up and go work on something else. Later, my phone rings, I answer it, and the clerk tells me whether my package has arrived. That’s asynchronous behavior.

Basic Asynchronous Behavior: Callback Functions and setTimeout

The usual way to implement asynchronous behavior is with a callback function. This is a function that the caller passes as an argument to the async function. When the async function completes, it invokes the callback function.

A bit of code will demonstrate how this works in JavaScript. For asynchronous behavior, we’ll use setTimeout, which executes its callback function asynchronously after a specified minimum amount of time has elapsed. (If that specified amount is zero, it still executes asynchronously.) We’ll also use confirm, which executes synchronously. In other words, confirm stops code from executing until Cancel or Ok is clicked.

We’ll start by setting up a couple of buttons in HTML:

And now, we’ll set up handlers for the two buttons:

Clicking the Sync button, and then OK in the confirm dialog, will log this:

Clicking the Async button, and then OK in the confirm dialog, will log this:

The confirm box represents a long-running item of code. It is synchronous, and so it blocks all other activity until the user selects Ok or Cancel. Running confirm from inside setTimeout‘s callback function removes the block to other synchronous code that follows the confirm lexically.

Internally, the call stack is responsible for executing function calls. The way that asynchronous calls work is that calls to their callbacks aren’t pushed directly on to the call stack. Instead, they are pushed into a message queue. The message queue monitors the call stack, waits until it is empty, and when it is, transfers calls from the queue to the stack so they can be run. This is why synchronous code runs before asynchronous code, and this is why setTimeout‘s callback executes last in the above example, even though its delay value is set to 0.

Using the XMLHttpRequest API to work with HTTP methods

One of the more common uses for async code is when fetching data from a server. The XMLHttpRequest API is one way to do this asynchronously. It uses events to accomplish this; for example, to fetch data it sends an HTTP GET method to a server, and it exposes a load event that fires when the data becomes available.

The XMLHttpRequest API can handle other HTTP methods such as POST, PUT and DELETE as well.

This code uses an XMLHttpRequest object to perform a GET request:

The responseType property formats the response as JSON. The send method actually sends the request. When the server sends a response, the load event fires and the handler logs the response to the console.

The Promise object

The Promise API decouples asynchronous behavior from implementation-specific details such as asynchronous interaction with server data (with the XMLHttpRequest API) or asynchronous reading of local files (with the FileReader API).

A Promise object represents a future result of an asynchronous operation. It has three possible states: pending, fulfilled and rejected. Once the operation has either completed or failed, the pending state changes to either fulfilled or rejected. Once that change is made, no further state changes are possible.

A Promise object exposes a then method, which has two parameters: onFulfilled and onRejected. Both of these accept a function that gets called when a Promise is either fulfilled or rejected: onFulfilled gets called when a Promise is fulfullled, and onRejected when it’s rejected.

A rejected Promise has a reason argument that cannot change. The Promise object provides a catch method, which is used to access this argument’s value.

A fulfilled Promise has a value argument that cannot change. The Promise object provides a then method, which is used to access this argument’s value.

The then method also returns a new Promise object, which means that Promise objects can be chained together with successive then calls.

Using the Fetch API to work with HTTP methods

The Fetch API works with the Promise API to interact with server data.

This bit of code uses the Fetch API to perform the same GET request as the above XMLHttpRequest example does:

Here’s how it works. The fetch method accepts a URL argument. If nothing else is specified, an HTTP GET request is sent to the provided URL. Once the request is sent, fetch returns a Promise object. This object is fulfilled when the URL returns a response, resolving to a Response object representing the response to the GET request. This object becomes the argument passed to the callback of the first then method. This callback converts the Response object’s data to JSON data (by calling Response.json()). This JSON data becomes the argument passed to the callback of the second then method.

The fetch method can accept an additional options parameter, which is an object containing name/value pairs of various options. We have to specify some of these additional options if we want to do anything other than a simple GET request. For example, to post data, we have to do something like this:

To use the POST method, it has to be specified in the method option. The body option contains the data to be posted. The headers option is an object containing name/value pairs of headers. If the server is expecting data in JSON format (which it may not although JSON is the most often-used format), we have to specify that in the Content-Type header.

async and await

The async/await syntax is syntactic sugar for the Promise object. This syntax has a few rules:
1. To create an asynchronous function, a function declaration or expression is preceded by the async keyword.
2. The await keyword can only be used inside a function that is declared with the async keyword.
3. The await keyword awaits the resolution of a Promise object, much like the then method.
4. The catch and finally methods of the Promise object are replaced by the standard try/catch/finally blocks.

This code uses async/await syntax to print strings asynchronously:

For comparison, this code uses a Promise object to do the same:

To use an anonymous function with the async keyword, we can wrap the function in an IIFE:

The advantage of async/await syntax is that it looks more like synchronous code than an explicitly created Promise object does. Each of the calls to printString looks the same, and the initial Promise object gets created “under the hood.”

Other Features of the Promise Object

The Promise object has a number of other important features. Of these, I will touch upon the methods that support async task concurrency.

The most commonly used of these is the Promise.all method. This method accepts an array of Promise objects, and returns a single Promise object. This Promise object fulfills if and when all of the input Promise objects fulfill, and rejects if any of the input Promise objects reject.

Other concurrency methods include any, allSettled and race.

For more information, see the documentation for the Promise object.