Introduction

Asynchronous JavaScript is used to effectively handle potential blocking operations such as fetching resources from a server. When only one thing happens at once, a function depending on another function has to wait for the other function to finish and return. From the perspective of the user, the program is essentially stopped during that time and a spinning cursor on the screen is generally visible.

This is a frustrating experience and a waste of computing processing power.Asynchronous programming lets you get other work done in the meantime. This is important in the case of web browsers. When a web app runs in a browser and it executes and intensive chunk of code without returning control to the browser, this one can appear to be frozen. This is called blocking: the browser is prevented from handling user input.

Modern software design increasingly revolves around asynchronous programming to allow programs to do more than one thing at a time. It used to be hard to write asynchronous code but it's gotten a lot easier. We'll explore further why asynchronous code matters and how to design code that avoids blocking, spinlocks and other processor power-wasting problems.

Prerequisites
Introducing async JavaScript

Synchronous JavaScript

To understand what asynchronous code is, we need to understand what synchronous code is. In this block of code, the lines are executed one after the other:


        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';
          document.body.appendChild(pElem);
        });
      

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

Asynchronous JavaScript

To avoid such blocking, many Web APIs now use asynchronous code, especially for fetching resources from an external device (accessing a database and returning data from it, for instance). There are two main types of asynchronous code:

Async callbacks

Callbacks are functions that are passed as parameters to other functions to be executed whena previous operation has returned. The second parameter of addEventListener() is an example:


        btn.addEventListener('click', () => console.log('I am the callback!'));
      

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

When we pass a callback function to another function, we only pass the function definition as the parameter, so that this 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.


        function loadAsset(url, type, callback) {
          let xhr = new XMLHttpRequest();
          xhr.open('GET', url);
          xhr.responseType = type;
          xhr.onload = () => callback(xhr.response);
          xhr.send();
        }

        function displayImage(blob) {
          //displaying the resource on the user interface
        }

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

Here the displayImage() function creates an image from the url's target and appends it to the document. The resource is fetched by the loadAsset() function and it passes the response to the callback to do something with it.

Callbacks are versatile, they allow you to control the order in which functions are run, but also how and what data is passed between them. Note that some callbacks run synchronously, like when Array.prototype.forEach() loops through the items of an array.


        const gods = ['Apollo', 'Artemis', 'Hermes', 'Zeus', 'Helios'];

        gods.forEach((godName, index) => {
          console.log(`${index} - ${godName}`);
        });
      

In this case, forEach() takes a unique callback as parameter and it runs immediately, without waiting for anything.

Promises

Promises are the new style of async code, used in modern web APIs. The fetch() API is a good example. It is basically a more modern and efficient version of XMLHttpRequest:


        fetch('../source/products.json')
          .then((response) => response.json())
          .then((json) => {
            products = json;
            initialize();
          })
          .catch((error) => console.log(`Fetch problem: ${error.message}`));
      

fetch() takes a single parameter, the url of the resource to fetch from the network, and returns a promise. The promise is an object representing the result (completion or failure) of the async operation. It represents an intermediate state. 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". Then we've got three further code blocks chained onto the end of the fetch() operation:

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.

Conclusion

At its most basic, JavaScript is a synchronous, blocking, single-threaded language: only one action 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 but when some kind of event occurs (passage of time, user's interaction with the mouse, arrival of data from the network, etc.). It means that you can let your code do several things at the same time without blocking the main thread.

Async loops and intervals

For a long time, JavaScript has made available a number of functions that allow you to asynchronously execute code after a certain time interval has elapsed, and repeatedly execute a block of code periodically until you tell it to stop. These are:

These functions actually run on the main thread, but you're able to run other code between iterations to a more or less efficient degree, depending on how processor intensive these operations are. These functions are used for running constant animations and other background processing on a web site or application.

setTimeout()

setTimeout() executes a specified block of code once after a specified time has elapsed. It takes the following parameters:

Here, the browser will wait for 2 seconds and display an alert message:


        const myGreeting = setTimeout(() => {
          alert('Hello Mr Universe!');
        }, 2000);
      

The function could be named and even defined somewhere else:


        function sayHi() {
          alert('Hello Mr Universe!');
        }

        const myGreeting = setTimeout(sayHi, 2000);
      

setTimeout() returns an identifier value that can be used to refer to the timeout later, to stop it for instance (see "Clearing timeouts", below).

Passing parameters to a setTimeout() function

Any parameter we want to pass to the function being run inside setTimeout() have to be passed as additional parameters, at the end of the list:


        function sayHi(who) {
          alert(`Hello ${who}!`);
        }

        let myGreeting = setTimeout(sayHi, 2000, 'Mr Universe');
      

The name of the person to say hello is passed as a third parameter.

Clearing timeouts

Finally, if a timeout has been created, you can cancel it before the specified time has elapsed by calling clearTimeout() and passing it the identifier of the setTimeout() call as a parameter:


        clearTimeout(myGreeting);
      

setInterval()

setInterval() runs a block of code over and over again. It works in a similar way to setTimeout(), except that the function passed to it as first parameter is executed repeatedly at an interval equal to the number of milliseconds provided as the second parameter. You can also pass any parameters required by the function being executed in as subsequent parameters of the setInterval() call.

The following function creates a new Date() object, extracts a time string out of it, using toLocaleTimeString() and then displays it in the UI. We then run it once per second using setInterval(), creating the effect of a digital clock that updates once per second.


        function displayTime() {
          let date = new Date();
          let time = date.toLocaleTimeString();
          document.getElementById('demo').textContent = time;
        }

        const createClock = setInterval(displayTime, 1000);
      

Clearing intervals

setInterval() keeps running a task forever. We may want to stop such tasks to prevent errors when the browser can't complete a cycle or of the animation being handled has finished. We can do it the same way as we stopped timeouts, by passing the identifier returned by setInterval() to the clearInterval() function:


        const myInterval = setInterval(myFunction, 2000);

        clearInterval(myInterval);
      

Things to keep in mind about setTimeout() and setInterval()

Recursive timeouts

setTimeout() can be called recursively to run the same code repeatedly, instead of using setInterval(). Here is an illustration:


        let i = 1;
        setTimeout(function run() => {
          console.log(i);
          i++;
          setTimeout(run, 100);
        }, 100);
      

Here is the version using setInterval():


        let i = 1;
        setInterval(() => {
          console.log(i);
          i++;
        }, 100);
      

The difference between the two versions of the code is a subtle one

When your code has the potential to take longer to run than the time interval you've assigned, it's better to use recursive setTimeout(). This will keep the time interval constant between executions regardless of how long the code takes to execute, and you won't get errors.

Immediate timeouts

Using 0 as the value of setTimeout() schedules the execution of the passed function as soon as possible, but only after the main thread has been run. In the following code, the alert "World" will only run after clicking OK on the alert "Hello":


        setTimeout(() => alert('World'), 0);
        alert('Hello');
      

This can be useful in cases where you want to set a block of code to run as soon as all of the main thread has finished running. Put it on the async event loop, so it will run straight afterward.

Clearing with clearTimeout() or clearInterval()

clearInterval() and clearTimeout() use the same list of entries to clear from. Interestingly enough, this means that you can use either method to clear a setTimeout() or setInterval(). But to avoid confusion and for consistency, use the appropriate method.

requestAnimationFrame()

requestAnimationFrame() is a specialized looping function created for running animations in the browser. It is basically the modern version of setInterval(). It executes a specified block of code before the browser next repaints the display, allowing an animation to be run at a suitable framerate regardless of the environment it is being run in.

The method takes a callback to be invoked before the repaint as argument. This is the general pattern you'll see it used:


        function draw() {
          // Drawing code goes here
          requestAnimationFrame(draw);
        }

        draw();
      

The idea is that you define a function in which your animation is updated, then you call it to start the process off. At the end of the function block, you call requestAnimationFrame() with the function reference passed as the parameter and this instructs the browser to call the function again on the next display repaint. This is then run continuously, as we are calling requestAnimationFrame() recursively.

We don't specify a time interval for requestAnimationFrame(). It just runs as fast and smoothly as possible in the current conditions (as close as possible to 60 fps - frames per second). The browser doesn't waste time running the animation if it is offscreen, etc.

Including a timestamp

The callback passed to the requestAnimationFrame() function also accepts a parameter: a timestamp value that represents the time since the animation started running. This is useful as this allows to run things at specific times and at a constant pace, regardless of how fast or slow the device might be.


        let startTime = null;
        function draw(timestamp) {
          if (!startTime) {
            startTime = timestamp;
          }
          currenTime = timestamp - startTime;
          // Do something based on current time
          requestAnimationFrame(draw);
        }
      

Clearing a requestAnimationFrame() call

Clearing a requestAnimationFrame() can be done calling the corresponding cancelAnimationFrame(), passing it the value returned by the requestAnimationFrame() (stored in a variable)


        cancelAnimationFrame(myRaF);
      
Async operations with Promises

A promise is an object that represents an intermediate state of an operation: a promise that a result of some kind will be returned at some point in the future.

The trouble with callbacks

To fully understand why promises are a good thing, it helps to think back why old-style callbacks are problematic.

Let's talk about ordering pizza as an analogy. There are certain steps that you have to take for your order to be successful, which don't really make sense to try to execute out of order, or in order but before each previous step has quite finished:

  1. You choose what pizza you want. This can take a while if you're indecisive and may fail if you can't make up your mind, or decide to get a curry instead.
  2. You then place your order. This can take a while to return a pizza and may fail if the restaurant does not have the required ingredients to cook it.
  3. You then collect your pizza and eat. This may fail if, says, you forgot your wallet so can't pay for the pizza!

With old-style callbacks, a pseudo-code representation of the above functionality might look something like this:


          choosePizza((order) => {
            placeOrder(order, (pizza) => {
              collectOrder(pizza, (enjoy) => {
                eatPizza(enjoy);
              }, failureCallback);
            }, failureCallback);
          }, failureCallback);
        

This is messy and hard to read (often referred to as "callback hell"). If this were real code, it would likely block the main thread until it completes and would require you to call the failureCallback() multiple times.

Improvements with promises

Promises make situation like the above much easier to write, parse and run. If we represented the above peudo-code using asynchronous promises instead, we'd end up with something like this:


          choosePizza()
            .then(order => placeOrder(order))
            .then(pizza => collectOrder(pizza))
            .then(enjoy => eatPizza(enjoy))
            .catch(failureCallback);
        

This is much better. It is easier to see what's going on and a single .catch() block is needed to handle all the errors. It doesn't block the main thread and each operation is guaranteed to wait for previous operations to complete before running. We're able to chain multiple asynchronous actions to occur one after another because each .then() block returns a new promise that resolves when the .then() block is done running.

At their most basic promises are similar to event listeners, but with a few differences:

  • A promise can only succeed or fail once. It cannot succeed or fail twice and it cannot switch from success to failure or vice versa once the operation has completed.
  • If a promise has succeeded or failed and you later add a success / failure callback, the correct callback will be called, even though the event took place earlier

Explaining basic promise syntax: a real example

Promises are important to understand because most modern Web APIs use them for returning values. Later on, we'll look at how to write your own promise, bu for now we'll look at some simple examples.

In the first example, we'll use the fetch() method to fetch an image from the web, the blob() to transform the fetch response's raw body contents into a Blob object, and then display that blob inside and <img> element. This is very similar to the very first example we looked at in this page.


          let promise = fetch('coffee.jpg');
        

This calls the fetch() method, passing it the url of the image to fetch from the network as a parameter. We are storing the promise object returned by fetch() inside a variable. This object represents an intermediate state that is initially neither success or failure. When a promise has not completed yet, we say it is pending

To respond to the successful completion of the operation, whenever that occurs (when a Response is returned in that case), we invoke the then() method of the promise object. The callback inside the then() block (referred to as the executor) runs only when the promise call completes successfully (when it is fulfilled, or resolved). It is passed the Response object as parameter. Note the then() doesn't run until an event occurs (when the promise fulfills). We immediately run the blob() method on this response to ensure that the response body is fully downloaded and then transform it into a blob object we can use.


          let promise2 = promise.then(response => response.blob());
        

Each call to then() creates a new promise and the call to blob() also returns a promise. We can handle the Blob object stored in the second variable by invoking the then() method of the second promise.


          let promise3 = promise2.then((myBlob) => {
            let objectURL = URL.createObjectURL(myBlob);
            let image = document.createElement('img');
            image.src = objectURL;
            document.body.appendChild(image);
          })
        

Here we are running the URL.createObjectURL() method. It is passed the Blob object returned by the second promise fulfillement, as a paramater. This will return a URL pointing to the object. Then we create an <img> element, set its src attribute to equal the object URL and append it to the DOM, so the image is displayed on the page.

Responding to failure

There is currently nothing to explicitly handle errors if one of the promises fails (rejects, in promise-speak). We can add error handling by running the .catch() method of the previous promise.


          let errorCase = promise3.catch((error) => {
            console.log(`There has been a problem with your fetch operation: ${error.message}`);
          });
        

This allows us to control error handling exactly how we want. In a real app, your catch() block could retry fetching the image, or show a default image, or prompt the user to provide another url, or whatever.

Chaining the blocks together

You can shorten this code and chain together then() and catch() blocks. The above code could be written like this:


          fetch('coffee.jpg')
            .then(response => response.blob())
            .then((myBlob) => {
              let objectURL = URL.createObjectURL(myBlob);
              let image = document.createElement('img');
              image.src = objectURL;
              document.body.appendChild(image);
            })
            .catch(error => console.log(`Error: ${error.message}`));
        

The value returned by the executor function passed to a then() block becomes the parameter passed to the next then() block's executor function. Note that .then() and .catch() blocks in promises are the async equivalent of a try...catch block in sync code.

Running code in response to multiple promises fulfilling

Let's look at some more advanced features. What if you want to run some code shortly after a whole bunch of promises have all fulfilled? You can do this with the Pomise.all() static method. It takes an array of promises as parameter and returns a new promise object that will fulfill only if all promises in the array fulfill:


        Promise.all(p1, p2, p3)
          .then((values) => {
            // do something here
          });
      

If all promises fulfill, the executor function will be passed an array "values as parameter, containing all the results. If any of the promises passed to Promise.all() reject, the whole block will reject. This can be very useful if you're fetching information to dynamically populate a UI. It generally makes sense to receive all the data rather than displaying partial information.

Here is a real world example:


          function fetchAndDecode(url, type) {
            return fetch(url).then((response) => {
              if (type === 'blob') {
                return response.blob();
              } else if (type === 'text') {
                return response.text();
              }
            })
            .catch(error => console.log(`Fetching error: ${error.message}`));
          }

          let coffee = fetchAndDecode('coffee.jpg', 'blob');
          let tea = fetchAndDecode('tea.jpg', 'blob');
          let description = fetchAndDecode('description.txt', 'text');

          Promise.all([coffee, tea, description])
            .then((values) => {
              // store each value returned from the promises in separate variables,
              // create object URLs from the blobs
              let objectURL1 = URL.createObjectURL(values[0]);
              let objectURL2 = URL.createObjectURL(values[1]);
              let descText = values[2];

              // display the images in <img> elements
              let image1 = document.createElement('img');
              let image2 = document.createElement('img');
              image1.src = objectURL1;
              image2.src = objectURL2;
              document.appendChild(image1);
              document.appendChild(image2);

              // display the text in a paragraph
              let para = document.createElement('p');
              para.textContent = descText;
              document.appendChild(para);
            });
        

First, we define a function being passed a url and the type of resource being fetched (blob or text). Inside the function, we call the fetch() function to fetch the resource at the specified url. In this case, the second promise we chain on is different depending on what the type value is. Additionally, we don't run the promise chain in isolation, we have added the return keyword before the fetch call. The effect is to run the entire chain and then run the final result (the promise returned by blob() or text()) as the return value of the function just defined. In effect, the return statements pass the results back up the chain to the top.

At the end of the block, we chain on a .catch() call, to handle any error that may come up with any of the promises passed in the array to .all(). If any of the promises reject, the catch block will let you know which one had a problem. The .all() block will still fulfill, but just won't display the resources that had problems. If you wanted the .all() to reject, you'd have to chain the .catch() block on to the end of it instead.

Next, we call our function three times to begin the process of fetching and decoding the images and text. Each of the returned promises is stored in a variable. Then we define a Promise.all() block to run some code only when all three of the promises stored above have successfully fulfilled. The executor inside the .then() will be passed an array containing the results from the individual promises (decoded response bodies).

Running some final code after a promise fulfills / rejects

There will be cases where you want to run a final block of code after a promise completes, regardless of whether it fulfilled or rejected. The .finally() can be chained onto the end of the regular promise chain, allowing you to cut down on code repetition of adding this final code to the end of both the .then() and .catch() blocks.


          myPromise
            .then(response => doSomething(response))
            .catch(error => handleError(error))
            .finally(() => runFinalCode());
        

In a real example, this would look like this:


          function fetchAndDecode(url, type) {
            return fetch(url).then((response) => {
              if (type === 'blob') {
                return response.blob();
              } else if (type === 'text') {
                return response.text();
              }
            })
            .catch(error => console.log(`Fetch failed: ${error.message}`))
            .finally(() => console.log(`Fetch attempt for ${url} finished.`))
          }
        

Building your own custom promises

Combining different promise-based APIs together to create custom functionality is by far the most common way you'll do custom things with promises. There is another way, however.

Using the Promise() constructor

It is possible to build your own promises using the Promise() constructor. The main situation in which you'll want to do this is when you've got code based on an old-school asynchronous API that is not promise-based, which you want to promis-ify.

Here is a simple example, where we wrap a setTimeout() with a promise. This runs a function after two seconds that resolves the promise with a string of "Success!".


        let timeoutPromise = new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("Success!");
          }, 2000);
        });
      

resolve() and reject() are functions that you call to resolve or reject the newly-created promise. So, when you call this promise, you can chain a .then() block onto its end an it will be passed the "Success!" string. In the below code, we simply alert that message:


        timeoutPromise()
          .then(message => alert(message));
      

or even just:


        timeoutPromise().then(alert);
      

Rejecting a custom promise

We can create a promise that rejects using the reject() method, taking a single value: the value to reject with, i.e. the error passed into the .catch() block.


        function timeoutPromise(message, interval) {
          return new Promise((resolve, reject) => {
            if (message === '' || typeof message !== 'string') {
              reject("Message is empty or not a string.");
            } else if (interval < 0 || typeof interval !== 'number') {
              reject("Interval is negative or not a number.");
            } else {
              setTimeout(() => resolve(message), interval);
            }
          })
        }
      

Here we are passing two values into a custom function, a message to do something with and a time interval before doing this thing. Inside the function, we return a new Promise object. Invoking the function will return the promise we want to use. Inside this Promise constructor, we do a number of checks:

  1. We check to see if the message is appropriate for being alerted. If it's an empty string or not a string at all, we reject the promise with a suitable error message
  2. Next, we check to see if the interval is an appropriate interval value. If it's negative or not a number, we reject the promise with a suitable error message.
  3. Finally, if the parameters are ok, we resolve the promise with the specified message after the specified interval has passed using setTimeout().

Since the timeoutPromise() function returns a Promise, we can chain .then(), .catch() blocks onto it to make use of its functionality.


        timeoutPromise('Hello there!', 1000)
          .then(message => alert(message))
          .catch(error => console.log(error));
      

A more real-world example

In the above example, the async nature is faked using setTimeout(). One example we'd like to invite you to study is Jake Archibald's idb library. This takes the IndexedDB API, which is an old-style callback-based API for storing and retrieving data on the client-side, and allows you to use it with promises. The following block converts the basice request model used by many IndexedDB methods to use promises:


          function promisifyRequest(request){
            return new Promise((resolve, reject) => {
              request.onsuccess = () => resolve(request.result);
              request.onerror = () => reject(request.error);
            });
          }
        

Conclusion

Promises are a good way to build asynchronous applications when we don't know the return value of a function or how long it will take to return. They make it easier to express and reason about sequences of asynchronous operations without deeply nested callbacks. And they support a style of error handling that is similar to the synchronous try / catch statement. We didn't touch on all promise features in this article, just the most interesting and useful ones. Most of modern Web APIs are promise-based, so you'll need to understand promises to get the most out of them. Among those APIs are WebRTC, Web Audio, Media Capture and Streams. Promises will be more and more important as time goes on, so learning to use and understand them is an important step in learning modern JavaScript.

Easier async programming with async await

async functions and the await keyword are part of the ECMAScript 2017 JavaScript edition. These features are basically synctatic sugar on top of promises, making async code easier to write and to read afterward. They make async code look more like old-school sync code.

The basics of async / await

There are two parts to async / await.

The async keyword

First of all, we have the async keyword, which you put in front of a function declaration to turn it into an async function. Try typing these lines into the browser's JavaScript console:


          function hello() {
            return 'Hello!';
          }
          hello();
        

The function returns "Hello!", nothing special. But if you turn it into an async function:


          async function hello() {
            return 'Hello!';
          }
          hello();
        

Invoking the function now returns a promise. You can also create an async function like so:


          let hello = async () => 'Hello!';
          hello();
        

To actually consume the value returned when the promise fulfills, since it's returning a promise, we cound use a .then() block.


          hello()
            .then(value => console.log(value));
        

or even just a shorthand such as:


          hello().then(console.log);
        

The await keyword

The real advantage of the async functions becomes apparent when you combine it with the await keyword. This can be put in front of any async promise-based function to make the code execution pause on that line until the promise fulfills, then the fulfillement value is returned. Here is a trivial example:


          async function hello() {
            return greeting = await Promise.resolve('Hello!');
          }
          hello().then(alert);
        

Rewriting promise code with async await

Let's look back at a simple fetch example.


          fetch('coffee.jpg')
            .then(response => response.blob())
            .then((myBlob) => {
              let objectURL = URL.createObjectURL(myBlob);
              let image = document.createElement('img');
              image.src = objectURL;
              document.appendChild(image);
            })
            .catch(error => console.log(`Error while fetching: ${error.message}`));
        

Let's convert this to use async / await to see how much simpler it makes things.


          async function myFetch() {
            let response = await fetch('coffee.jpg');
            let myBlob = await response.blob();

            let objectURL = URL.createObjectURL(myBlob);
            let image = document.createElement('img');
            image.src = objectURL;
            document.appendChild(image);
          }
        

It makes code much simpler and easier to understand. No more .then() blocks everywhere!

Since the async keyword turns a function into a promise, you could refactor your code to use a hybrid approach of promises and await, bringing the second half of the function out into a new block, to make it more flexible:


          async function myFetch() {
            let response = await fetch('coffee.jpg');
            return await response.blob();
          }

          myFetch()
           .then((blob) => {
              let objectURL = URL.createObjectURL(myBlob);
              let image = document.createElement('img');
              image.src = objectURL;
              document.body.appendChild(image);
           });
        

But how does it work?

It is necessary to wrap the code inside a function and to include the async keyword before the function keyword. You have to create an async function to define a block of code in which you'll run your async code; await only works inside of async functions.

Inside the myFetch() function, instead of needing to chain a .then() block on to the end of each promise-based method, you just need to add an await keyword before the method call and then assign the result to a variable. The await keyword causes the JavaScript parser to pause on this line until the async call has returned its result, then once it is complete, move on to the next line. So, for example:


          let response = await fetch('coffee.jpg');
        

The response returned by fetch() is assigned to a variable when it is available, and the parser pauses on this line until that occurs. Once the response is available, the parser moves to the next line, which creates a blob out of it. This line also invokes an async promise-based method, so we use await there as well. When the result of the operation returns, we return if out of the myFetch() function.

This means that when we call the myFetch() function, it returns a promise, so we can chain a .then() onto the end of it, inside which we handle displaying the blob onscreen.

Adding error handling

If you want to add error handling, you've got a couple of options. You can use the familiar sync try / catch structure:


          async function myFetch() {
            try {
              let response = await fetch('coffee.jpg');
              let myBlob = await response.blob();

              let objectURL = URL.createObjectURL(myBlob);
              let image = document.createElement('img');
              image.src = objectURL;
              document.body.appendChild(image);
            } catch(error) {
              console.log(error);
            }
          }

          myFetch();
        

The catch() block is passed an error object that can be logged to the console. Using the refactored hybrid version of the code, you'd be better off chaining a .catch()block onto the end of the .then() block like this:


          async function myFetch() {
            let response = await fetch('coffee.jpg');
            return await response.blob();
          }

          myFetch()
           .then((blob) => {
              let objectURL = URL.createObjectURL(myBlob);
              let image = document.createElement('img');
              image.src = objectURL;
              document.body.appendChild(image);
           })
           .catch(error => console.log(error));
        

This is because the .catch() block will catch errors occurring in both the async function call and the promise chain. If you used the try / catch block here, you might still get unhandled errors in the myFetch() function when called.

Awaiting a promise.all

These keywords, async / await, are compatible with Promise.all(). So, you can happily await a Promise.all() call to get all results returned in a variable in a way that looks like simple sync code. Let's convert an example we already saw previously:


          async function fetchAndDecode(url, type) {
            let response = await fetch(url);
            let content;

            if (type === 'blob') {
              content = await response.blob();
            } else if (type === 'text') {
              content = await response.text();
            }

            return content;
          }

          async function displayContent() {
            let coffee = fetchAndDecode('coffee.jpg', 'blob');
            let tea = fetchAndDecode('tea.jpg', 'blob');
            let description = fetchAndDecode('description.txt', 'text');

            let values = await Promise.all([coffee, tea, description]);

            let objectURL1 = URL.createObjectURL(values[0]);
            let objectURL2 = URL.createObjectURL(values[1]);
            let descText = values[2];

            let image1 = document.createElement('img');
            let image2 = document.createElement('img');
            image1.src = objectURL1;
            image2.src = objectURL2;
            document.body.appendChild(image1);
            document.body.appendChild(image2);

            let para = document.createElement('p');
            para.textContent = descText;
            document.body.appendChild(para);
          }

          displayContent()
            .catch(error => console.error(error));
        

Only a few changes were needed. See the Promise.all() line:


          let values = Promise.all([coffee, tea, description]);
        

By using await here, we are able to get all the results of the three promises returned into the values array, when they are all available, in a way that looks very much like sync code.

The downsides of async / await

Async / await is really useful to know about, but there are a couple of downsides to consider.

They make your code look asynchronous, and in a way, it makes it behave more synchronously. The await keyword blocks the execution of all the code after it until the promise fulfills, exactly as it would with a synchronous operation. This means that your code could be slowed down by a significant number of awaited promises happening straight after another. Each await will wait after for the previous one to finish, whereas actually what you want is for the promises to begin processing simultaneously like they would do if we weren't using async / await.

There is a pattern that can mitigate this problem ‐ setting off all the promise processes by storing the Promise object into variables, and then awaiting them afterwards. Let's analyze two examples, slow and fast async / await, both starting off with a custom function that fakes an async process with a setTimeout() call:


          function timeoutPromise(interval) {
            return new Promise((resolve, reject) => {
              setTimeout(() => resolve("done!"), interval);
            });
          }
        

Then each one includes a timeTest() async function that awaits three timeoutPromise() calls:


        async function timeTest() {
          // function body here
        }
      

Each one ends by recording a start time, seeing how long the timeTest() promise takes to fulfill, then recording and end time and reporting how long the operation took in total.


        let startTime = Date.now();
        timeTest().then(() => {
          let finishTime = Date.now();
          let timeTaken = finishTime - startTime;
          alert(`Time taken in milliseconds: ${timeTaken}`);
        })
      

It is the timeTest() that differs in each case. In the slow async / await example, it looks like this:


        async function timeTest() {
          await timeoutPromise(3000);
          await timeoutPromise(3000);
          await timeoutPromise(3000);
        }
      

Here, we simply await all three timeoutPromise() calls directly, making each one alert for 3 seconds. Each subsequent one is forced to wait until the last one finished. If your run the first example, you'll see the alert box reporting a total run time of about 9 seconds.

In the fast async / await example, timeTest() looks like this:


          const timeoutPromise1 = timeoutPromise(3000);
          const timeoutPromise2 = timeoutPromise(3000);
          const timeoutPromise3 = timeoutPromise(3000);

          await timeoutPromise1;
          await timeoutPromise2;
          await timeoutPromise3;
        

Here we store the three Promise objects in variables, which has the effect of setting off their associated processes all running simultaneously. Next, we await their results. Because the promises all started processing at the same time, the promises will all fulfill at the same time.Running the second example, you'll see the alert box reporting a total run time of just over 3 seconds!

Another minor inconvenience is that you have to wrap your awaited promises inside an async function.

Async / await class methods

You can even add async in front of class / object methods to make them return promises and await promises inside them.


          class Person {
            constructor(first, last, age, gender, interests) {
              this.name = {first, last};
              this.age = age;
              this.gender = gender;
              this.interests = interests;
            }

            async greeting() {
              return await new Promise.resolve(`Hi! I'm ${this.name.first}`);
            }

            farewell() {
              console.log(`${this.name.first} has left the building. Bye for now!`);
            }
          }

          let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling']);
        

This first class method can be used like this:


          han.greeting()
            .then(console.log);
        

Summary

Async / await provide a nice, simplified way to write async code that is simpler to read and maintain. It is well worth learning and considering for use.