Using async/await in Node.js 7.6.0

Last week saw the release of Node.js v7.6.0 which contained (amongst other things) an update to v8 5.5 (Node's underlying JS engine). This v8 release includes a brand new language feature: async functions. Utilising this new feature is done through 2 new keywords: async and await. Async functions are not new in the JS world and it has been possible to use them via transpilers (babel) for some time, but having them natively in the language means their use will become much more widespread.

So what is an async function?

Async functions makes promise-using code more readable than before. Now instead of using Promise.then() to resolve your promise to a value, you can just prefix the promise with await and your code magically pauses until the value is available, then proceeds with execution as if the value were synchronous all along. To allow this to happen, you need to use the async keyword on the enclosing function.

Here's a before and after example of some code encrypting a password using bcrypt and updating a user's password to the hash:

Before:

schema.pre('save', function hashPassword(next) {
  const user = this;

  // only hash the password if it has been modified (or is new)
  if (!user.isModified('password')) return next();

  // generate a salt
  return bcrypt.genSalt(10).then((salt) => {
    // hash the password along with our new salt
    return bcrypt.hash(user.password, salt).then((hash) => {
      // override the cleartext password with the hashed one
      user.password = hash;
      return next();
    }).catch(next);
  }).catch(next);
});

And heres the same code as before rewritten to use async functions:

schema.pre('save', async function hashPassword(next) {
  try {
    const user = this;

    // only hash the password if it has been modified (or is new)
    if (!user.isModified('password')) return next();

    // generate a salt
    const salt = await bcrypt.genSalt(10);

    // hash the password along with our new salt
    const hash = await bcrypt.hash(user.password, salt);

    // override the cleartext password with the hashed one
    user.password = hash;
    return next();
  } catch (e) {
    return next(e);
  }
});

Note the async keyword before the function, and await when we need to resolve a promise to a value.

This code is flatter and easier to reason about. Error handling is performed using a single try..catch block as opposed to adding .catch(next) to the end of each promise.

Caveats

There are some caveats to using this approach. If you're not using promises everywhere already, then you'll need to wrap your calling code in a function then execute this function as a promise. Consider the following express route handler to fetch a user by id:

Before async functions:

app.get('/:id', (req, res, next) => {
  User.findById(req.params.id).then(user => {
    res.json(user)
  }).catch(next)
})

Because express isn't promise-aware, you have to use a wrapping function to catch any errors and pass them onto next:

app.get('/:id', (req, res, next) => {
  async function action() {
    const user = await User.findById(req.params.id)
    res.json(user)
  }
  action().catch(next)
})

This is a little cleaner, but has the same level of nesting as before. This can be improved using a simple little helper function, to make express become promise-aware:

function asyncWrap(fn) {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
};

app.get('/:id', asyncWrap(async (req, res) => {
  const user = await User.findById(req.params.id)
  res.json(user)
})

This unifies the error handling to a single place and keeps it out of the route handler. If User.findById() throws an error, it'll bubble up and get passed along to the error middleware as you'd expect.

To conclude

I think async/await are game changers for the JS language. Although it's possible to write clean code using callbacks and promises, it can muddy the intent when it comes to error checking (either through repeated if (err) return cb(err) or .catch(cb) calls). Conversely however, async/await makes it really easy to write code with lots of async operations which could cause performance issues and be signs of deeper architectural problems within your program.

Node.js 7.6.0 changelog: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V7.md#2017-02-21-version-760-current-italoacasas
v8 5.5 changelog: https://v8project.blogspot.com/2016/10/v8-release-55.html

Stay In-Touch

Want to hear from us about APIs, documentation, DX and what's new at ReadMe?