ES7 Async Await in JavaScript

This is a blog post on the async and await functionality coming to JavaScript. It is currently on Stage 3 Draft but is well adopted in tools such as Babel which transpiles the code into Generators (which is actually transpiled again by Babel to backward support legacy browsers). The development of this proposal is happening at https://github.com/tc39/ecmascript-asyncawait, if you’d like to track it further.

async / await #

The aysnc and await pattern is essentially a decorator on top of promises and generators to have an even cleaner solution to the callback hell anti-pattern.

Promises provide a layer of abstraction to handle callbacks. To build on top of that, together with Generators, the async / await pattern handles writing asynchronous code in JavaScript.

Async Functions internally leverage both generators and promises.

Traditional Promise-based Architecture (ES6) #

export default function getMagicalProfile() {
  return new Promise((resolve, reject) => {
    getName().then((name) => {
      getHouseFrom({name})
      .then(resolve)
      .catch((err) => {
        resolve(handleError(new ErrorMsg(name)));
      });
    }, reject);
  });
}

New Async-based Architecture (ES7) #

export default async function getMagicalProfile() {
  const name = await getName();
  const house = await getHouseFrom({name});
  return buildProfile({name, house});
}

*With the try/catch neatly abstracted within the function

While async functions make it easier to write asynchronous code, it also pushes the developer into writing code that is synchronous. This is because multiple await expressions in it will be suspended one at a time on each await expression until that Promise is resolved before unsuspending execution and moving onto the next await expression.

Promise.all #

The solution to multiple await functions in an async function is to use Promise.all (mdn) to handle all the Promises of the two or more tasks running concurrently.

async function getMagicalProfile() {
  const [name, house, stats] = await Promise.all([getName(), getHouse(), runSortingHat()]);

  res.json({name, house, stats});
}

With getName(), getHouse(), runSortingHat() all returning promises while they asynchronously retrieve or run their respective deferred object or task. Since Promise.all() returns a promise, we can still await on it.

Use Cases #

Syntactic Sugar #

Let’s be honest here, a lot of the new flashy ES6 and ES7 functionality are merely syntactic sugar (as evidenced by Babel being able to just transpile it). async / await makes the code written to be more procedural rather than get caught in the callback hell or promise pit. It aids a lot in the readability of the code over anything else. And this is important since readable code is usually a top concern among software developers.

Leveraging Try Catch #

Async Functions also effectively encapsulates error case scenarios nicely for Promise-based application architectures which aids in the readability of the app. However note that errors are swallowed “silently” within an async function just like in Promises.

Async functions are able to leverage try / catch functionality by wrapping each await function in a try / catch. Note that any error, either inside an await or in the body of the async function will reject the promise returned by the async function.

async function getMagicalProfile() {
  try {
    await Promise.all([getName(), getHouse(), runSortingHat()]);
  } catch (err) {
    console.log(`10 points from Gryffindor because of ${err}`);
  }
}

Not blocking the UI thread #

Yes, yes while Promises and yes even a mere callback does this for us already, async / await is another layer of abstraction on top of that to handle the load of mimicking a totally multi-threaded environment with concurrent programming.

“Weaponizing” Generators #

async / await relies on Promises and Generators to function. However while Promises have been heavily used in the wild before, Generators have often been looked as overly complicated and unusable. The async / await pattern allows developers to easily use the ES6 Generator feature in their common day to day projects without going into the intricacies of the feature.

The algorithm behind async / await iterates over the generator sequence wrapping each item in the sequence in a promise and then chaining that with the next step in the sequence. (more)

ES7 Async #
async function example(a, b, c) {
  ...
}
Transpiled to ES6 #
function example(a, b, c) {
  return spawn(function* () {
    ...
  }, this);
}

function spawn(genF, self) {
  return new Promise(function (resolve, reject) {
    var gen = genF.call(self);
    step(() => gen.next(undefined));

    function step(nextF) {
      var next;
      try {
        next = nextF();
      } catch(e) {
        // finished with failure, reject the promise
        reject(e);
        return;
      }
      if (next.done) {
        // finished with success, resolve the promise
        resolve(next.value);
        return;
      }
      // not finished, chain off the yielded promise and `step` again
      Promise.resolve(next.value).then(
        v => step(() => gen.next(v)),
        e => step(() => gen.throw(e))
      );
    }
  });
}

So we recursively call step to keep chaining yielded promises until it is over.

TL;DR #

Understand Promises before using async / await. Also look around at Generators and how they fit into the async / await pattern. Use this pattern to simplify processes that rely heavily on Promises.

Every async function you write will return a promise, and every single thing you await will ordinarily be a promise

To learn how to set this up, rauchg made a quick MVP with async on Node 6 here

 
85
Kudos
 
85
Kudos

Now read this

Building a Slack Bot in Golang

The Background # This blog post details the steps in which I’ve built my first Slack Bot with less than trivial functionality. It was created over a hackathon and was then released on open source as Pricelinelabs’s leaderboard project... Continue →