async & await

By  on  

JavaScript promises were a revelation in JavaScript, providing an alternative to the JavaScript callback hell we'd quickly found ourselves in.  Promises also allowed us to introduce and better handle asynchronous tasks.  While promises were an improvement over callback hell, they still require lots of thens which can become messy.  I've really taken to ES2017's async and await keywords for simplifying promise handling.  Let's have a look at async and await!

Quick Basics

  • async is a keyword for the function declaration
  • await is used during the promise handling
  • await must be used within an async function, though Chrome now supports "top level" await
  • async functions return a promise, regardless of what the return value is within the function
  • async/await and promises are essentially the same under the hood
  • Available now in most browsers as well as Node.js

Benefits of async and await

  • Your code is more simplistic, precise
  • Debugging is easier thanks to less callbacks
  • Conversion from promise then / catch code is easy
  • Your code can be more "top down", less nesting

Basic async and await Illustration

It's always easiest to learn from an example, so let's have a look at a very simple async / await usage:

// Function declared as async so await can be used
async function fetchContent() {
  // Instead of using fetch().then, use await
  let content = await fetch('/');
  let text = await content.text();
  
  // Inside the async function text is the request body
  console.log(text);

  // Resolve this async function with the text
  return text;
}

// Use the async function
var promise = fetchContent().then(...);

Start by declaring the function as async; this declaration allows await to be used from within.  The await keyword is then followed by a promise-yielding action, which of course the fetch API is.  The asynchronous routine (fetch in this case) runs and execution of further code halts (though not blocking) until the async action finishes.  The function then resolves with the return value and a promise is returned.

Essentially you get to keep your code "inline" without the need for callbacks.  It's async made a bit more simple!

Converting Promise Handling to await

There's a good chance you'll want to update your promise code when time becomes available.  Let's walk through updating promise to await:

// Before: callback city!
fetch('/users.json')
  .then(response => response.json())
  .then(json => {
    console.log(json);
  })
  .catch(e => { console.log('error!'); })

// After: no more callbacks!
async function getJson() {
  try {
    let response = await fetch('/users.json');
    let json = await response.json();
    console.log(json);
  }
  catch(e) {
    console.log('Error!', e);
  }
}

The conversion from loads of thens to await is simple to execute and your code hopefully looks a bit maintainable!

async / await Patterns

There are a number of ways you can declare async functions.

Anonymous Async Function

let main = (async function() {
  let value = await fetch('/');
})();

Async Function Declaration

async function main() {
  let value = await fetch('/');
};

Async Function Assignment

let main = async function() {
  let value = await fetch('/');
};

// Arrow functions too!
let main = async () => {
  let value = await fetch('/');
};

Async Function as Argument

document.body.addEventListener('click', async function() {
  let value = await fetch('/');
});

Object & Class Methods

// Object property
let obj = {
  async method() {
    let value = await fetch('/');
  }
};

// Class methods
class MyClass {
  async myMethod() {
    let value = await fetch('/');
  }
}

As you can see, adding async is really easy and accommodates all function creation workflows!

Error Handling

Traditional promise use allows you to use a catch callback to handle rejection.  When you use await, your best bet is using try/catch:

try {
  let x = await myAsyncFunction();
}
catch(e) {
 // Error!
}

The old try/catch isn't as glamorous as a promise's catch callback but is just as effective.

Parallelism

Google's Jake Archibald make excellent points in the Async functions document about not getting too sequential with your awaits.  The idea is to avoid stacking awaits, when possible, and instead trigger tasks immediately and use await after said tasks are triggered:

// Will take 1000ms total!
async function series() {
  await wait(500);
  await wait(500);
  return "done!";
}

// Would take only 500ms total!
async function parallel() {
  const wait1 = wait(500);
  const wait2 = wait(500);
  await wait1;
  await wait2;
  return "done!";
}

The first block is bad because the second wait happens after the the first wait completes.  The second block is a better method: trigger both wait calls and then use await ; doing so allows the async functions to happen concurrently!

Promise.all Equivalents

One of my favorite functions of the Promise API is Promise.all, which fires a callback when all fetches are complete.  There's no direct async / await equivalent but this post provides a good equivalent:

let [foo, bar] = await Promise.all([getFoo(), getBar()]);

Remember that async / await are essentially the same as promises deep down, so we're simply awaiting the aggregated promise to be resolved!

You can now use async and await in all major browsers.  These new keywords are also available within Node.js; older Node.js versions can use the transform-async-to-generator babel plugin to use async and await today.  Promises are still excellent but are made more maintainable with async and await!

Recent Features

  • By
    Write Simple, Elegant and Maintainable Media Queries with Sass

    I spent a few months experimenting with different approaches for writing simple, elegant and maintainable media queries with Sass. Each solution had something that I really liked, but I couldn't find one that covered everything I needed to do, so I ventured into creating my...

  • By
    JavaScript Promise API

    While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?  Promises are becoming a big part of the JavaScript world...

Incredible Demos

  • By
    Redacted Font

    Back when I created client websites, one of the many things that frustrated me was the initial design handoff.  It would always go like this: Work hard to incorporate client's ideas, dream up awesome design. Create said design, using Lorem Ipsum text Send initial design concept to the client...

  • By
    Fx.Rotate:  Animated Element Rotation with MooTools

    I was recently perusing the MooTools Forge and I saw a neat little plugin that allows for static element rotation: Fx.Rotate. Fx.Rotate is an extension of MooTools' native Fx class and rotates the element via CSS within each A-grade browser it...

Discussion

  1. Shahar taite

    Hey David,
    Long time fan of the blog,
    Question about the alternative for Promise.all – if we would define multiple async methods and await them one after the other like in your parallelism example wouldn’t any following code wait for all promises to resolve like Promise.all?
    Thanks

    • Hi Shahar,

      With the alternative, all of the requests are made asynchronously. The requests are fired of sequentially, but the code doesn’t stop to wait for them to resolve until the await statement.

  2. If you want to call two async functions and have them run in parallel, then assign the function call to a variable then on a new line, await that variable.

    const request1 = getUsers();
    const request 2 = getOtherThings();
    
    const response1 = await request1;
    const response2 = await request2;
    

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!