Async/Await Arrive in Firefox

The new async and await keywords—which make asynchronous code more concise, obvious, and maintainable—have arrived in Firefox 52. Currently available in the latest Developer Edition release, Firefox 52 is scheduled for general release in March 2017.

JavaScript owes its excellent single-threaded performance and responsiveness on the web to its pervasively asynchronous design. Unfortunately, that same design gives rise to “callback hell,” where sequential calls to asynchronous functions require deeply nested, hard-to-manage code, as seen in this slightly contrived example using the localforage library:

function foo(callback) {
  localforage.setItem('x',  Math.random(), function(err) {
    if (err) {
      console.error("Something went wrong:", err);
    } else {
      localforage.getItem('x', function(err, value) {
        if (err) {
          console.error("Something went wrong:", err);
        } else {
          console.log("The random number is:", value);
        }

        if (callback) {
          callback();
        }
      });
    }
  });
}

foo(function() { console.log("Done!"); });

If you glossed over that code, or didn’t immediately understand what it did, that’s the problem.

ES2015 began addressing this challenge by standardizing on Promises for chained, asynchronous functions. Since their introduction, Promises have become an integral part of new web standards, including fetch and service workers. They make it possible to rewrite the previous example as:

function foo() {
  return localforage.setItem('x', Math.random())
         .then(() => localforage.getItem('x'))
         .then((value) => console.log("The random number is:", value))
         .catch((err) => console.error("Something went wrong:", err));
}

foo().then(() => console.log("Done!"));

Thanks to Promises, the code doesn’t nest deeper with each successive call, and all of the error handling can be consolidated into a single case at the end of the chain.

Note that in the example above, foo() returns immediately, before localforage does its work. Because foo() itself returns a Promise, future callbacks can be scheduled for after it completes with the .then() method.

Semantically, the example above is much more straightforward, but syntactically, there’s still a lot to read and understand. The new async and await keywords are syntactic sugar on top of Promises to help make Promises more manageable:

async function foo() {
  try {
    await localforage.setItem('x', Math.random());
    let value = await localforage.getItem('x');
    console.log("The random number is:", value);
  } catch (err) {
    console.error("Something went wrong:", err);
  }
}

foo().then(() => console.log("Done!"));

The code above is functionally identical to the previous example, but it is much easier to understand and maintain, since the function body now resembles a common, synchronous function.

Functions marked async always return Promises, and thus calls to .then() work on their return value to schedule callbacks. Expressions prefixed with await effectively pause functions until the expression resolves. If an awaited expression encounters an error, then execution passes to the catch block. If uncaught, the returned Promise settles into a rejected state.

Similarly, instead of handling errors inside async functions, it’s possible to use normal .catch() methods on the return value instead:

async function foo() {
    await localforage.setItem('x', Math.random());
    let value = await localforage.getItem('x');
    console.log("The random number is:", value);
}

foo().catch(err => console.error("Something went wrong:", err))
     .then(() => console.log("Done!"));

For a more practical example, consider a function you might write to unsubscribe a user from web push notifications:

function unsubscribe() {
  return navigator.serviceWorker.ready
         .then(reg => reg.pushManager.getSubscription())
         .then(subscription => subscription.unsubscribe())
         .then(success => {
           if (!success) {
             throw "unsubscribe not successful";
           }
         });
}

With async and await, it becomes:

async function unsubscribe() {
  let reg = await navigator.serviceWorker.ready;
  let subscription = await reg.pushManager.getSubscription();
  let success = await subscription.unsubscribe();
  if (!success) {
    throw "unsubscribe not successful";
  }
}

Both function identically, but the latter example hides the complexities of Promises, and turns asynchronous code into code that reads (and executes) like synchronous code: from top to bottom, waiting for each line of code to fully resolve before moving on to the next line.

Native cross-browser support for async and await keywords is still nascent, but you can use them today with the help of a JavaScript transpiler like Babel, which can convert async / await to functionally equivalent, backward-compatible code.

To learn more about the async and await keywords, or Promises in general, check out the following resources:

Remember, async and await are just helpers for Promises: you can mix and match either syntax, and everything you learn about Promises applies directly to  async and await.

Special thanks to Jamund Ferguson for suggesting improvements to the code samples in this post.

About Dan Callahan

Engineer with Mozilla Developer Relations, former Mozilla Persona developer.

More articles by Dan Callahan…


10 comments

  1. CrystalGamma

    Do note however, that async/await are used to build a serial chain of execution, just asynchronous.
    If you want to make use of concurrent requests to reduce latency, you still need to use other mechanisms like Promises and callbacks.
    Luckily, as async/await is just syntax sugar for Promises, the interoperation looks to be quite painless.

    December 7th, 2016 at 12:06

    1. Dan Callahan

      Great point! Because async/await are just sugar over Promises, you can use await with lower-level, concurrent methods like Promise.all() and Promise.race(), and those functions can accept async functions as arguments.

      December 7th, 2016 at 12:23

      1. voracity

        Couldn’t Promise.all() have its own syntactic sugar? Something like:

        for (let v of await promises) {

        }

        which would represent:

        Promise.all(promises).then(function(values) {
        for (let v of values) {

        }
        return nextPromiseIfThereIsOne;
        })

        As I was waiting for await (for a looong time), I chose to avoid promises when possible, so forgive me if the code is incorrect.

        I guess Promise.race() would be a highly specialised need, so wouldn’t need its own sugar.

        December 9th, 2016 at 22:28

        1. Dan Callahan

          There was a proposal for await* [...] which would have desugared into await Promise.all([...]), but it was removed before the spec was finalized. For now, we’re limited to the more explicit Promise.all(), which might not be a bad thing in terms of usability / discoverability.

          December 12th, 2016 at 09:13

  2. Matt Gale

    It’s good that it’s getting support in browsers now. Personally I think that promises are simpler to read that async/await, so I won’t be switching. It’s nice to have options though.

    December 8th, 2016 at 03:50

    1. Dan Callahan

      Definitely use what makes sense for you, just don’t forget that you can switch over to async functions if things get messy. Especially in cases where you need to reference the results of several Promises in one function, like “Advanced Mistake #4” on We Have a Problem with Promises, async functions make it far easier to have useful variable scoping.

      December 8th, 2016 at 10:23

      1. Matt Gale

        Good to know. Thanks

        December 8th, 2016 at 13:49

  3. Mindaugas J

    You know what else can pause execution in the middle of a function?

    Generators.

    The code would look basically identical, just replace await for yield and function* for async function. All you need is a good generator runner. The benefit is, all current browsers support them.

    Anyway, I have already tried writing async/await code in node (with `—harmony_async_await`) and I am never doing callback/promise soup again.

    December 8th, 2016 at 12:13

    1. Dan Callahan

      You’re right that generators can be used to hack around missing support for async functions: that’s exactly what Babel’s transform-async-to-generator plugin does. However, using generators on IE or older Safari versions requires the relatively heavyweight Regenerator polyfill. It works, but it’s a hack and adds unnecessary complexity. For this reason, the Koa web framework is switching to async functions from generators as soon as they’re available in Node.js, and projects like fast-async are implementing alternative ways of transpiling async functions in Babel.

      December 8th, 2016 at 15:14

  4. Andrei

    For me Async await is the most exciting thing in the new JS.

    People who say “I’m fine with promises” didn’t write enough asynchronous code to really appreciate async await. Async await is great because it makes writing and maintaining asynchronous code much easier, something which our brains is not really good at.

    December 11th, 2016 at 09:12

Comments are closed for this article.