Async/await resources

Table of contents

Part of “Learn to write async code in JavaScript.”

Introductory articles

Jake Archibald on async functions

Async functions - making promises friendly.

Notes from Jake’s article

  • “Anything you await is passed through Promise.resolve(), so you can safely await non-native promises.” Example: Logging a fetch.

  • Async functions always return a promise. For example, if you return a string, you get back a promise that fulfills with that string. Async return values

  • If you pass an async function to .map, and you await a promise inside, it won’t wait every promise to resolve, but instead, will run all of them in parallel. By the way—I say that, not Jake—the same is true for forEach loops. If you want to wait for each separate promise to resolve in an iteration, you can use a for-of loop. You can find more on await in loops in this StackOverflow thread.

  • Avoid going too sequential. Prefer the following code:

    async function logInOrder(urls) {
      // fetch all the URLs in parallel
      const textPromises = urls.map(async (url) => {
        const response = await fetch(url);
        return response.text();
      });
    
      // log them in sequence
      for (const textPromise of textPromises) {
        console.log(await textPromise);
      }
    }

    Over this code:

    // NOT reccomended.
    async function logInOrder(urls) {
      for (const url of urls) {
        const response = await fetch(url);
        console.log(await response.text());
      }
    }

    By the way, the first snippet is a replacement for the map + reduce pattern from the promises page—and it’s way more readable.

My observations

  • The lines after a statement with await can be viewed as the code inside a then callback. That code executes asynchronously as a micro-task. Each consecutive await after the first can be viewed as another then in the promise chain. Consider this example:

    (async () => {
      const result = await Promise.resolve(3);
      console.log(result);
      const double = await Promise.resolve(result * 2);
      console.log(double);
    })();
    
    (async () => {
      console.log("Synchronous");
      const result = await Promise.resolve(4);
      console.log(result);
    })();

    The code above prints:

    /**
     * - Synchronous
     * - 3
     * - 4
     * - 6
     */

    The equivalent code with promises is the following:

    Promise.resolve(3)
      .then((res) => {
        console.log(res);
        return res * 2;
      })
      .then((double) => console.log(double));
    
    console.log("Synchronous");
    Promise.resolve(4).then((res) => console.log(res));
  • Error handling in async/await when we have a nested function that uses a promise: As long as every promise is awaited, the root catch will catch all the nested errors. If you don’t await a promise—a use case is to start parallel requests—you won’t catch the error in the parent. You’ll have to try-catch inside the async function you’re not awaiting. Or use a pattern like the Promise.all(array of promises) or the map + for-of loop you saw earlier. Some examples that illustrate that point:

const failingPromise = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(Error("Promise failed."));
    });
  });
};

const nestedAwait = async () => {
  await failingPromise();
  return 2;
};

// 1. Parent catch works.
(async () => {
  try {
    await nestedAwait();
  } catch (err) {
    console.log(err);
  }
})();

// // 2. It also works without nesting.
// (async () => {
//   try {
//     await failingPromise();
//   } catch (err) {
//     console.log(err);
//   }
// })();

// // 3. Doesn't work, it throws an UnhandledPromiseRejectionWarning.
// const nestedDiscardThePromise = async () => {
//   failingPromise();
//   return 2;
// };

// (async () => {
//   try {
//     await nestedDiscardThePromise();
//   } catch (err) {
//     console.log(err);
//   }
// })();

// // 4. A nested map also discards the promises without giving
// // the chance to catch them.
// const nestedMapDiscardsPromises = async () => {
//   const data = [1, 2, 3];
//   const promises = data.map(async item => {
//     await failingPromise();
//   });
//   return promises;
// };

// (async () => {
//   try {
//     await nestedMapDiscardsPromises();
//   } catch (err) {
//     console.log(err);
//   }
// })();

// 5. But with map and for-of we can catch the errors (the first at least).
// const nestedMapForOf = async () => {
//   const data = [1, 2, 3];
//   const promises = data.map(async item => {
//     await failingPromise();
//   });

//   // eslint-disable-next-line no-restricted-syntax
//   for (const promise of promises) {
//     await promise;
//   }
//   return 2;
// };

// (async () => {
//   try {
//     await nestedMapForOf();
//   } catch (err) {
//     console.log(err);
//   }
// })();

Other things to read

Popular

Previous/Next