Wednesday, 7 December, 2016 UTC


Summary

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, which makes 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. Since their introduction, Promises have become an integral part of new web standards, including fetch and service workers.
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 the whole function rejects, and execution passes to the catch block above, or the entire function call settles into a rejected state.
For instance, 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 === true, unsubscribed... */ 
         })
         .catch(err => {
           /* Unsubscription failed */
         });
}
With async and await, it becomes:
async function unsubscribe() {
  try {
    let reg = await navigator.serviceWorker.ready;
    let subscription = await reg.pushManager.getSubscription();
    let success = await sub.unsubscribe();
    if (success) {
      /* if success === true, unsubscribed... */
    }
  } catch {
      /* unsubscription failed */
  }
}
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:
  • MDN: Async functions
  • Can I Use: Async functions
  • PouchDB: We have a problem with Promises
  • Promisees: An interactive Promise visualization by @ponyfoo.
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.