Async /await is a really good thing. With it, writing asynchronous code in Node.js is silky smooth, eliminating the need to import third-party libraries to help, and eliminating complex Promise chain calls. What are the application scenarios of async/await? What are the best practices? This article tells you all about it.

Retry failed requests

Sends HTTP requests in Node and retries the specified number of times if the request fails. The callback would look something like this:

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);
  });
}
Copy the code

It’s not too hard, but with recursion, it’s a little tricky for beginners. Also, there is a small problem: what if the superagent.get().end() function call itself throws a synchronization exception? We might need to wrap a try/catch layer around _request() to catch all exceptions. You need to do this everywhere you call this method, which is a bit cumbersome and error-prone. Compare this with async/await code, which requires only a for loop and a try/catch:

const superagent = require('superagent');

const NUM_RETRIES = 3;

test(a); asyncfunction 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
}
Copy the code

The neat thing about this code is that the request exits the for loop with no exceptions, otherwise it goes through the next loop until the specified number of times is exceeded. No callbacks, no recursion, doesn’t it look much clearer? Note, however, that await must be used inside async functions, nor nested include. The forEach() callback in the following code is not async and therefore cannot use await:

const superagent = require('superagent');

const NUM_RETRIES = 3;

test(a); asyncfunction 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) {}
  });
}
Copy the code

Process the MongoDB cursor

MongoDB’s find() returns a cursor. A cursor is actually an object with an asynchronous next() function that fetches the next document in the query result. If all results are fetched, next() returns NULL. The MongoDB cursor has several helper functions, such as each(), map(), and toArray(), and Mongoose ODM adds an additional eachAsync(), but these are just syntactically sugar above next().

Without async/await, manually calling next() to retry a failed request also uses recursion, as in the previous example. With async/await, you’ll find that you don’t have to use helper functions (except toArray()) anymore, because you just iterate through the cursor with the for loop, simple and straightforward!

const mongodb = require('mongodb');

test(a); asyncfunction 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'}]); Const cursor = db.collection('Movies').find(); // Move cursor with 'next()' and 'await'for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
    console.log(doc.name);
  }
}
Copy the code

If that’s not convenient enough, you can also use async iterators (a feature introduced in ES2018 and now supported by Node.js 10.x).

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

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

Execute asynchronous tasks concurrently

The examples above all execute multiple requests sequentially, with only one next() function executing at any one time. How can I execute multiple asynchronous tasks concurrently? Suppose you want to use bcrypt to perform multiple password hashes simultaneously:

const bcrypt = require('bcrypt');

const NUM_SALT_ROUNDS = 8;

test(a); asyncfunction test() {
  const pws = ['password'.'password1'.'passw0rd']; // 'Promises' is a promise array,' bcrypt.hash() ' 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));
}
Copy the code

The promise.all () function takes an array of promises as an argument and returns a Promise. After multiple incoming promises have been resolved, the returned promise is resolved and an array containing the execution results of each promise is received. Since every bcrypt.hash() returns a promise, promises are an array of promises, and await promises.All (promises) is the result of every bcrypt.hash() call.

Promise.all() is not the only way to handle multiple asynchronous functions in parallel. The promise.race () function can also execute multiple promises in parallel, except that it is resolved after the Promise that was first completed. A race literally means “a race” to see which promise is fulfilled first. Here is an example of promise.race () with async/await:

/**
 * Prints below:
 * waited 250
 * resolved to 250
 * waited 500
 * waited 1000
 */
test(a); asyncfunction 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;
}
Copy the code

Note that while promise.race () is resolved after the first fulfilled Promise, the rest of the async functions continue to execute. Because promises are currently unenforceable.

conclusion

Async/await is considered the ultimate solution for JavaScript asynchronous programming. This feature not only reduces the amount of code, but also greatly improves readability. It has great advantages in exception handling, retrying requests and performing concurrent tasks, and is worth popularizing.

See this rather energetic logo, don’t pay attention to it?