Hello, my name is Seth Bergman. I am a

Full Stack Engineer

focused on helping companies scale. I love learning about software architecture, containers, open source programming and automation. I use technologies that drive innovation, speed up development and provide continuous delivery of awesome software.

Level Up Using Async / Await with the MEAN Stack

This post details a new feature in Node.js that make callbacks and promise libraries like Bluebird and co a thing of the past. All of the modern browsers support the new version of Node.js, v8.0.0 so it's just native JavaScript code according to the browser. This means that you will no longer have to depend on 3rd party libraries to manage promises in your code.

Another great perk of async/await in Node.js is how well it integrates with existing npm libraries. By now, most popular Node.js libraries support some sort of promise-based API, so they integrate nicely with async/await.

You might even have the pleasure of removing a few dependencies from your package.json if you start using async/await instead of co.

In this article, I'll show you how async/await works with [mocha] https://www.npmjs.com/package/mocha) tests, express routes and middleware, and mongoose queries and cursors.

Mocha

Mocha has enjoyed rudimentary support for tests that return promises since 2014. Since async functions return promises, mocha has had support for async test functions since 2014. For example, if you wrote tests to test a login API endpoint using superagent using vanilla promises, it would look something like this:

const agent = require('superagent');

describe('login()', function() {  
  it('success', function() {
    const params = {
      email: '[email protected]',
      password: 'helloworld'
    };
    // Return a promise
    return agent.post('/login', params).
      then(token => {
        assert.ok(token);
        assert.ok(token._id);
      });
  });
});

This test works just fine in mocha, the test will succeed if the promise resolves and fail if the promise rejects. Before async/await, you could use co-mocha to seamlessly integrate with co:

const agent = require('superagent');  
const coMocha = require('co-mocha');  
const mocha = require('mocha');

coMocha(mocha);

describe('login()', function() {  
  it('success', function*() {
    const params = {
      email: '[email protected]',
      password: 'helloworld'
    };

    const token = yield agent.post('/login', params);

    assert.ok(token);
    assert.ok(token._id);
  });
});

With co-mocha, you don't need promise chaining, but you do need an outside library. With async/await though, since async functions return a promise, you don't need any outside libraries, the below test just works:

const agent = require('superagent');

describe('login()', function() {  
  it('success', async function() {
    const params = {
      email: '[email protected]',
      password: 'helloworld'
    };

    const token = await agent.post('/login', params);

    assert.ok(token);
    assert.ok(token._id);
  });
});

You can also use async functions with mocha before(), beforeEach(), etc. hooks. Using mocha with async/await is painless.

Express

Using async/await with the Express web framework is more subtle than it looks. You might naively think that the below code is perfectly fine:

const { MongoClient } = require('mongodb');  
const express = require('express');

async function run() {  
  const app = express();

  app.get('*', async function(req, res) {
    res.send('Hello, World!');
  });

  app.listen(3000);
  console.log('App listening');
}

run().catch(error => console.error(error.stack));  

However, what happens if the route handler throws an exception?

app.get('*', async function(req, res) {  
  throw new Error('test');
  res.send('Hello, World!');
});

Any incoming HTTP request will hang forever, because Express doesn't handle promise rejections for you. Express error handlers don't help either.

app.get('*', async function(req, res) {  
  throw new Error('test');
  res.send('Hello, World!');
});

// Will **not** get called because of the above error
app.use(function(err, req, res, next) {  
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

Unfortunately, as far as I know, there's no way to make the above code work without patching app.get(). However, with a more promise-friendly design pattern and a helper function, you can pass async function errors to Express error handlers.

app.get('*', wrap(async function(req) {  
  throw new Error('test');
}));

// Will **not** get called because of the above error
app.use(function(err, req, res, next) {  
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

// Resolves the wrapped async function and catches the errors
function wrap(fn) {  
  return function(req, res, next) {
    fn(req).then(returnVal => res.send(returnVal)).catch(next);
  };
}
Make sure to .catch() any errors and pass them along to the next() middleware in the chain, in this case the error handler.

The key idea in the above code is that, since async functions return a promise, you need to .catch() any promise rejections and pass them along to next().

Remember that Express error handlers are only triggered by errors passed to next(), not exceptions that you throw.

In general, when using Express with async/await, or its ES6 cousin co/yield, I find it much easier to separate out using res from the actual logic of the route handler.

Like the wrap() function above, I prefer to put business logic in the async function and call res.json() or res.send() in the .then() function. This makes error handling cleaner, because then you know you won't double-call res.send() if some error in the route handler happened after you called res.send().

Similarly, using async/await for middleware is easy with a wrapper function.

For example, suppose you wanted to log the method and URL from the HTTP request to MongoDB:

app.use(wrapMiddleware(async function(req) {  
  await db.collection('logs').insertOne({
    createdAt: new Date(),
    method: req.method,
    url: req.url
  });
}));

function wrapMiddleware(fn) {  
  return function(req, res, next) {

 /* If promise resolves, call `next()` with no args, otherwise 
 call `next()` with the error from the promise rejection */

    fn(req).then(() => next(), next);
  };
}

Mongoose

Queries in Mongoose 4.x have a .then() function, so you don't need any extra work to use mongoose with async/await:

const mongoose = require('mongoose');

async function run() {

/* No need to `await` on this, mongoose 4 handles connection 
   buffering internally */

  mongoose.connect('mongodb://localhost:27017/test');

  await mongoose.connection.dropDatabase();

  const MyModel = mongoose.model('Test', new 
  mongoose.Schema({name: String }));

  await MyModel.create({ name: 'Val' });

  // Prints an array with 1 element, the above document
  console.log(await MyModel.find());
}

run().catch(error => console.error(error.stack));  

Async/await makes interacting with mongoose cursors much more elegant. While you still can use cursors as a stream with async/await, it's much more elegant to use the next() function.

Fundamentally, a mongoose cursor is an object with a next() function that returns a promise which resolves to the next document in the query result, or null if there are no more documents.

const mongoose = require('mongoose');

async function run() {  
  mongoose.connect('mongodb://localhost:27017/test');

  await mongoose.connection.dropDatabase();

  const MyModel = mongoose.model('Test', new mongoose.Schema({ 
  name: String }));

  await MyModel.create({ name: 'Val' }, { name: 'Seth' });

  const cursor = MyModel.find().sort({name: 1 }).cursor();

  for (let doc = await cursor.next(); doc != null;
    doc = await cursor.next()) {
    console.log(doc.name); // Prints "Val" followed by "Seth"
  }
}

run().catch(error => console.error(error.stack));  
Review:

A cursor has a .next() function that returns a promise. The promise will resolve to the next doc if there is one, or null if there are no more results.

Mongoose cursors also have a neat eachAsync() function that lets you do some rudimentary functional programming with async/await.

The eachAsync() function executes a potentially async function for each document that the cursor returns. If that function returns a promise, it will wait for that promise to resolve before getting the next document. This is the easiest way to exhaust a cursor in mongoose.

const mongoose = require('mongoose');

async function run() {  
  mongoose.connect('mongodb://localhost:27017/test');

  await mongoose.connection.dropDatabase();

  const MyModel = mongoose.model('Test', new mongoose.Schema({ name: String }));

  await MyModel.create({ name: 'Val' }, { name: 'Seth' });

  const cursor = MyModel.find().sort({name: 1 }).cursor();

  let count = 0;
  console.log(new Date());
  await cursor.eachAsync(async function(doc) {

   /* Wait 1 second before printing first doc, and 0.5 before 
      printing 2nd  */

  await new Promise(resolve => setTimeout(() => resolve(), 
    1000 - 500 * (count++)));
    console.log(new Date(), doc);
  });
}

run().catch(error => console.error(error.stack));  

Moving Forward

Async/await is a powerful tool that integrates seamlessly with some, but not all, popular npm modules. I can imagine those libraries that choose not to support this awesome new feature will die out rather quickly.

I do feel that JavaScript is becoming more functional, like lower level languages. Now go and code the interwebs using async/await!