Introducing async JavaScript

This page is not complete.

In the first article of this module we look at what synchronous and asynchronous code are (sync and async), and how they differ. We'll also examine traditional synchronous methods of handling a sequence of operations, see what the problems are, and begin to understand how async techniques can help us solve such problems.

Prerequisites: Basic computer literacy, a reasonable understanding of JavaScript fundamentals.
Objective: To gain familiarity with what asynchronous JavaScript is, how it differs from synchronous JavaScript, and what use cases it has.

Synchronous JavaScript

To allow us to understand what asynchronous code is, we ought to start off by making sure we understand what synchronous code is.

A lot of the functionality we have looked at in previous modules is synchronous (or sync) — you run some code, and the result is returned as soon as the browser can do so. Let's look at a simple example (see it live here, and see the source):

const btn = document.querySelector('button');
btn.addEventListener('click', () => {
  alert('You clicked me!');

  let pElem = document.createElement('p');
  pElem.textContent = 'This is a newly-added paragraph.';

In this block, the lines are executed one after the other:

  1. We grab a reference to a <button> element that is already available in the DOM.
  2. We add a click event listener to it so that when it is clicked:
    1. An alert message appears.
    2. Once the alert is dismissed, we create a <p> element.
    3. We then give it some text content.
    4. Finally, we append the paragraph to the document body.

While each operation is being processed, nothing else can happen — rendering is paused. This is because client-side JavaScript is a single threaded (only one thing can happen at once, on a single main thread), blocking language (everything else is blocked until an operation completes).

So in the example above, after you've clicked the button the paragraph won't appear until after the OK button is pressed in the alert box. You can try it for yourself:


Note: It is important to remember that alert(), while being very useful for demonstrating a synchronous blocking operation, is terrible for using in real world applications.

Asynchronous JavaScript

For reasons illustrated earlier (e.g. related to blocking), many Web API features now use asynchronous code to run, especially those that influence or fetch some kind of resource from an external device, such as fetching a file from the network, accessing a database and returning data from it, accessing a video stream from a web cam, or broadcasting the display to a VR headset.

There are two main types of asynchronous code style you'll come across in JavaScript code, old-style callbacks and newer promise-style code. In the below sections we'll review each of these in turn.

Async callbacks

Async callbacks are functions that are passed as parameters to other functions to be executed when a previous operation has returned. These are slightly old-fashioned now, but you'll still see them in use in a number of older-but-still-commonly-used APIs.

An example of an async callback is the second parameter of addEventListener() (as we saw in action above):

btn.addEventListener('click', () => {
  alert('You clicked me!');

  let pElem = document.createElement('p');
  pElem.textContent = 'This is a newly-added paragraph.';

The first parameter is the type of event to be listened for, and the second parameter is a function that is invoked when the event is fired.

When we pass a callback function as a parameter to another function, we are only passing the function definition as the parameter — the callback function is not executed immediately. It is “called back” (hence the name) asynchronously, somewhere inside the containing function’s body. The containing function is responsible for executing the callback function when the time comes.

You can write your own function containing a callback easily enough. Let's look at another example that loads a resource via the XMLHttpRequest API (run it live, and see the source):

function loadAsset(url, type, callback) {
  let xhr = new XMLHttpRequest();'GET', url);
  xhr.responseType = type;

  xhr.onload = function() {


function displayImage(blob) {
  let objectURL = URL.createObjectURL(blob);

  let image = document.createElement('img');
  image.src = objectURL;

loadAsset('coffee.jpg', 'blob', displayImage);

Here we create a displayImage() function that simply represents a blob passed to it as an object URL, then creates an image to display the URL in, appending it to the document's . However, we then create a loadAsset() function that takes a callback as a parameter, along with a URL to fetch and a content type. It then fetches then resource at the given URL, before then passing the response to the callback to do something with. In this case the callback is waiting on the XHR call to finish downloading the resource (using the onload event handler) before it passes it to the callback.

Callbacks are versatile — not only do they allow you to control the order in which functions are run and data is passed between them, it also allows you to pass data to different functions depending on circumstance. So you could have different actions to run on the response downloaded, such as processJSON(), displayText(), etc.

Note that not all callbacks are async — some run synchronously. A example is when we use Array.prototype.forEach() to loop through the items in an array (see it live, and the source):

const gods = ['Apollo', 'Artemis', 'Ares', 'Zeus'];

gods.forEach(function (eachName, index){
  console.log(index + '. ' + eachName);

In this example we loop through an array of Greek gods and print the index numbers and values to the console. The expected parameter of forEach() is a callback function, which itself takes two parameters, a reference to the array name and index values. However, it doesn't wait for anything — it runs immediately.


Promises are the new style of async code that you'll see used in modern Web APIs. A good example is the fetch() API, which is basically like a modern, more efficient version of XMLHttprequest. Let's look at a quick example, from our Fetching data from the server article:

fetch('products.json').then(function(response) {
  return response.json();
}).then(function(json) {
  products = json;
}).catch(function(err) {
  console.log('Fetch problem: ' + err.message);

Note: You can find the finished version on GitHub (see the source here, and also see it running live).

fetch() takes a single parameter — the URL of a resource you want to fetch from the network — and it returns a promise. The promise is an object representing the completion or failure of the async operation. It represents an intermediate state, as it were. In essence, it's the browser's way of saying "I promise to get back to you with the answer as soon as I can," hence the name "promise."

This is a slightly strange concept. Neither of the possible outcomes have happened yet, so the fetch operation is currently waiting on the result of the browser trying to complete the operation at some point in the future. We've then got three further code blocks chained onto the end of the fetch():

  • Two then() blocks. Both contain a callback function that will run if the fetch operation is successful, and each callback receives as input the result of the previous successful operation, so you can go forward and do something else to it. Each .then() block returns another promise, meaning that you can chain multiple .then() blocks onto each other, so multiple async operations can be made to run in order, one after another.
  • The catch() block at the end runs if any of the .then() blocks fail — in a similar way to try...catch, an error object is made available inside, which can be used to report the kind of error that has ocurred.

Async operations like promises are put into an event queue, which runs after the main thread has finished processing and so does not block subsequent JavaScript code from running. The queued operations will complete as soon as possible then return their results to the JavaScript environment.

Note: You'll learn a lot more about promises later on in the module, so don't worry if you don't understand them fully for now.

Promises versus callbacks

Promises have some similarities to old-style callbacks. They are essentially a returned object to which you attach callback functions, rather than having to pass callbacks into a function.

However, promises are custom-made for handling async operations, and have many advantages over old-style callbacks:

  • You can chain multiple async operations together using multiple .then() operations, passing the result of one into the next one as an input. This is much harder to do with callbacks, and ends up with a messy "pyramid of doom" [FIND A LINK TO LINK TO FOR MORE INFORMATION]
  • Promise callbacks are always called in the strict order they are placed in the event queue.
  • Error handling is much better — all errors are handled by a single catch() block at the end of the block, rather than being individually handled in each level of THE "pyramid".


The nature of async code

Let's explore an example that further illustrates the nature of async code, showing what can happen when we are not fully aware of code execution order and the problems of trying to treat async code like sync code. The following example is fairly similar to what we've seen before (see it live, and the source). One difference is that we've included a number of console.log statements to illustrate an order that you might think the code would execute in.

console.log ('Starting');
let image;

fetch('coffee.jpg').then((response) => {
  console.log('It worked :)')
  return response.blob();
}).then((myBlob) => {
  let objectURL = URL.createObjectURL(myBlob);
  image = document.createElement('img');
  image.src = objectURL;
}).catch((error) => {
  console.log('There has been a problem with your fetch operation: ' + error.message);

console.log ('All done!');

The browser will begin executing the code, see the first console.log() statement (Starting) and execute it, and then initialize the image variable.

It will then move to the next line and begin executing the fetch() block but, because it is async and not blocking, it will move on with the code execution, finding the last console.log statement (All done!) and outputting it to the console.

Only once the fetch() block has completely finished running and delivering its result through the .then() blocks will we finally see the second console.log() message (It worked ;)) appear. So the messages have appeared in a different order to what you'd expect:

  • Starting
  • All done!
  • It worked :)

If this confuses you, then consider the following smaller example: console.log("registering click handler"); button.addEventListener('click', () => { console.log("get click"); }); console.log("all done"); This is very similar in behavior — the first and third console.log() messages will be shown immediately, but the second one is blocked from running until someone clicks the button. The previously example works in the same way, except that in that case the second message is blocked on the promise chain fetching a resource then displaying it on screen.

In a less trivial code example, this kind of setup could cause a problem — you can't include an async code block that returns a result, which you then rely on later in a sync code block. You just can't guarantee that the async function will return before the browser has processed the async block.

To see this in action, try taking a local copy of the example, and changing the third console.log() call to the following:

console.log ('All done! ' + image + 'displayed.');

You should now get an error in your console instead of the third message:

TypeError: image is undefined; can't access its "src" property

This is because at the time the browser tries to run the third console.log() statement, the fetch() block has not finished running so the image variable has not been given a value.

Active learning: make it all async!

To fix the problematic fetch() example and make the three console.log() statements appear in the desired order, you could make the third console.log() statement run async as well. This can be done by moving it inside another .then() block chained onto the end of the second one, or by simply moving it inside the second then() block. Try fixing this now.

Note: If you get stuck, you can find an answer here (see it running live also).


JavaScript is at its most basic a synchronous, blocking, single-threaded language — only one operation can be in progress at a time. But web browsers define functions and APIs that allow us to register functions that should not be executed synchronously, and should instead be invoked asynchronously when some kind of event occurs (the passage of time, the user's interaction with the mouse, or the arrival of data on the network, for example). This means that you can let your code do several things at the same time without stopping or blocking your main thread.

Whether we want to run code synchronously or asynchronously will depend on what we're trying to do.

There are times when we want things to load and happen right away. For example when applying some user-defined styles to a webpage you'll want the styles to be applied as soon as possible.

If we're running an operation that takes time however, like querying a database and using the results to populate templates, it is better to push this off the main thread and complete the task asynchronously.

In this module

  • Introducing Async JavaScript
  • Async loops and intervals
  • Handling async operations gracefully with Promises
  • Easier async code with async and await

Document Tags and Contributors

Contributors to this page: chrisdavidmills, Sheppy
Last updated by: chrisdavidmills,