Design Patterns in Node.js: Async/Await

Design Patterns in Node.js: Async/Await

Async/await in Node.js opens up a host of powerful design patterns. In this post, you'll see common Async/Await Design Patterns in Node.js

Originally published by Valeri Karpov at http://thecodebarbarian.com

Retrying Failed Requests

The power of await is that it lets you write asynchronous code using synchronous language constructs. For example, here's how you might retry a failed HTTP request using the superagent HTTP library using callbacks.

const superagent = require('superagent');

const NUM_RETRIES = 3;

request('http://google.com/this-throws-an-error', function(error, res) {  console.log(error.message); // "Not Found" });

function request(url, callback) {  _request(url, 0, callback); }

function _request(url, retriedCount, callback) {  superagent.get(url).end(function(error, res) {    if (error) {      if (retriedCount >= NUM_RETRIES) {        return callback && callback(error);      }      return _request(url, retriedCount + 1, callback);    }    callback(res);  }); }

Not too difficult, but it involves recursion and can be tricky to grok for beginners. Plus, there's another more subtle issue. What happens if superagent.get().end() throws a synchronous exception? We'd need to wrap the _request() call in a try/catch in order to handle all exceptions. Having to do this everywhere is cumbersome and error prone. With async/await, you can write an equivalent function with just for and try/catch:

const superagent = require('superagent');

const NUM_RETRIES = 3;

test();

async function test() {  let i;  for (i = 0; i < NUM_RETRIES; ++i) {    try {      await superagent.get('http://google.com/this-throws-an-error');      break;    } catch(err) {}  }  console.log(i); // 3 }

Trust me, this works. I remember the first time I tried this pattern with co, I was baffled that it actually worked. However, the below does not work. Remember that await must always be in an async function, and the closure passed to forEach() below is not async.

const superagent = require('superagent');

const NUM_RETRIES = 3;

test();

async function test() {  let arr = new Array(NUM_RETRIES).map(() => null);  arr.forEach(() => {    try {      // SyntaxError: Unexpected identifier. This await is not in an async function!      await superagent.get('http://google.com/this-throws-an-error');    } catch(err) {}  }); }

Processing a MongoDB Cursor

MongoDB's find() function returns a cursor. A cursor is fundamentally an object with an asynchronous next() function that gets the next document in the query result. If there are no more results, next() resolves to null. MongoDB cursors have several helper functions like each(), map(), and toArray(), and the mongoose ODM adds an additional eachAsync() function, but these are all just syntactic sugar on top of next().

Without async/await, calling next() manually involves the same kind of recursion as the retry example. With async/await, you'll find yourself not using the helper functions anymore (other than maybe toArray()) because iterating through the cursor with a for loop is much easier:

const mongodb = require('mongodb');

test();

async function test() {  const db = await mongodb.MongoClient.connect('mongodb://localhost:27017/test');

 await db.collection('Movies').drop();  await db.collection('Movies').insertMany([    { name: 'Enter the Dragon' },    { name: 'Ip Man' },    { name: 'Kickboxer' }  ]);

 // Don't await, instead get a cursor  const cursor = db.collection('Movies').find();  // Use next() and await to exhaust the cursor  for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {    console.log(doc.name);  } }

If that's not convenient enough for you, there's a TC39 proposal for async iterators that would let you do something like this. Note that the below code does not work in any currently released version of Node.js, it's just an example of what may be possible in the future.

const cursor = db.collection('Movies').find().map(value => ({
 value,
 done: !value
}));

for await (const doc of cursor) {  console.log(doc.name); }

Multiple Requests in Parallel

Both of the above patterns execute requests in sequence, there's only one next() function call executing at any given time. What about multiple asynchronous tasks in parallel? Let's pretend you're a malicious hacker and want to hash multiple plaintext passwords in parallel with bcrypt.

const bcrypt = require('bcrypt');

const NUM_SALT_ROUNDS = 8;

test();

async function test() {  const pws = ['password', 'password1', 'passw0rd'];

 // promises is an array of promises, because bcrypt.hash() returns a  // promise if no callback is supplied.  const promises = pws.map(pw => bcrypt.hash(pw, NUM_SALT_ROUNDS));

 /**    * Prints hashed passwords, for example:    * [ '$2a$08$nUmCaLsQ9rUaGHIiQgFpAOkE2QPrn1Pyx02s4s8HC2zlh7E.o9wxC',       '$2a$08$wdktZmCtsGrorU1mFWvJIOx3A0fbT7yJktRsRfNXa9HLGHOZ8GRjS',       '$2a$08$VCdMy8NSwC8r9ip8eKI1QuBd9wSxPnZoZBw8b1QskK77tL2gxrUk.' ]    */  console.log(await Promise.all(promises)); }

The Promise.all() function takes an array of promises, and returns a promise that waits for every promise in the array to resolve and then resolves to an array that contains the value each promise in the original array resolved to. Each bcrypt.hash() call returns a promise, so promises in the above array contains an array of promises, and the value of await Promise.all(promises) is the result of each of the bcrypt.hash() calls.

Promise.all() is not the only way you can handle multiple async functions in parallel, there's also the Promise.race() function that executes multiple promises in parallel, waits for the first promise to resolve, and returns the value that promise resolved to. Here's an example of using Promise.race() with async/await:

/

  • Prints below:
  • waited 250
  • resolved to 250
  • waited 500
  • waited 1000
  • / test();

async function test() {  const promises = [250, 500, 1000].map(ms => wait(ms));  console.log('resolved to', await Promise.race(promises)); }

async function wait(ms) {  await new Promise(resolve => setTimeout(() => resolve(), ms));  console.log('waited', ms);  return ms; }

Note that, although Promise.race() resolves after the first promise resolves, the remaining async functions still continue executing. Remember that promises are not cancellable.

Moving On

Async/await is a huge win for JavaScript. With these two simple keywords you can remove numerous external dependencies and hundreds of lines of code from your codebase. You can add robust error handling, retries, and parallelization with just a handful of simple built-in language constructs.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

Best 50 Nodejs interview questions from Beginners to Advanced in 2019

Node.js 12: The future of server-side JavaScript

An Introduction to Node.js Design Patterns

7 best JavaScript Design Patterns You Should Know

Design patterns in Node.js: a practical guide



node-js web-development design-pattern

What's new in Bootstrap 5 and when Bootstrap 5 release date?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Random Password Generator Online

HTML Color Picker online | HEX Color Picker | RGB Color Picker

Top Node.js Development Companies and Expert NodeJS Developers

A thoroughly researched list of top NodeJS development companies with ratings & reviews to help hire the best Node.JS developers who provide development services and solutions across the world. List of Leading Node.js development Service Providers...

Hire Node JS Developer from Expert Node JS Development Company

NodeJS Development Company-Hire Node JS developer from the most prominent NodeJS development company, Mobiweb and get remarkable Node.js app development services.

An Introduction to Node.js Design Patterns

Design patterns are part of the day to day of any software developer, whether they realize it or not. In this article, we will look at how to identify these patterns out in the wild and look at how you can start using them in your own projects.

Hire Dedicated eCommerce Web Developers | Top eCommerce Web Designers

Build your eCommerce project by hiring our expert eCommerce Website developers. Our Dedicated Web Designers develop powerful & robust website in a short span of time.

Main Reasons of Using Node JS for Your Web Application Development

You have to hire Node JS developer from prestigious and expert Node JS development company Mobiweb Technologies. They are tech enthusiasts with new and latest programming ideas, web development technologies and industry trends.