Node.js — Run Async Functions/Promises in Sequence

Code Sample

Async functions are part of Node.js since version 7.6. They received a boost in attention with the stable release of Node.js v8 because it’s one of the major new features.

When using async functions in your projects, you may encounter situations where you want to serialize the processing. The individual functions should run asynchronously but in sequence.

This tutorial shows you two ways to run async functions or promises in sequence.

Node.js Series Overview

Code Samples

You can find running code examples in the nodejs-tutorials repository on GitHub. You can run both files in the linked directory:

node index.js

# or

node with-errors.js  

Have fun!

Preparations

The examples in this tutorial simulate asynchronous processing by waiting for a period of milliseconds. This waiting time imitates the functionality you may run in your application, e.g., fetch data from the database, calling an external API, loading files from disk.

The function simulating an asynchronous request is called asyncProcessing(ms) and accepts an integer as a parameter. The parameter represents the waiting time in milliseconds:

async function asyncProcessing (ms) {  
  await new Promise(resolve => setTimeout(ms, resolve))

  console.log(`waited: ${ms}ms`)

  return ms
}

The upcoming examples use the asyncProcessing() function.

Approach 1: Run Promises in Sequence Using “for…of”

The integrated for…of loop allows you to run promises or async functions in sequence. it iterates through a list of items and you can then await the async function:

async function forOf() {  
  const timeouts = [10, 600, 200, 775, 125, 990]

  for (const timeout of timeouts) {
    result.push(await asyncProcessing(timeout))
  }

  return result
}


async function asyncProcessing (ms) {  
  await new Promise(resolve => setTimeout(ms, resolve))

  console.log(`waited: ${ms}ms`)

  return ms
}

The example above takes each item from the timeouts array and passes it as a parameter to the asyncProcessing function. While in the loop, you must await the individual function calls to serialize them.

Approach 2: Run Promises in Sequence using “Array.reduce()”

You may know Array.reduce() to run an action on each item in the list and also carry through the result of the previous processing.

Combining the benefits of reducing an array and running asynchronous operations allows you to streamline the processing. The gimmick here: you must await the call of asyncProcessing and also the carry.

In this example, the carry variable represents the accumulator. The async callback function returns a promise and this promise will be passed as the accumulator on the next item:

const timeouts = [10, 600, 200, 775, 125, 990]

async function reduce() {  
  const result = await timeouts.reduce(async (carry, timeout) => {
    return [
      …(await carry),
      await asyncProcessing(timeout)
    ]
  }, Promise.resolve([]))
}


async function asyncProcessing (ms) {  
  await new Promise(resolve => setTimeout(ms, resolve))

  console.log(`waited: ${ms}ms`)

  return ms
}

Please notice that you must also await the .reduce(). The final return value will be a promise and you must await it to retrieve the result.

Decide which handling you like more and enjoy serializing async functions in your application!

Error Handling

When running a list of asynchronous functions in sequence and one of the functions fails, the processing will stop at the failing item. The remaining items in the list won’t be touched if you don’t have proper error handling in place.

Both code examples above are optimistic implementations. There’s no try/catch block to catch any error and handle it. Ensure that you’re not blowing up your app by skipping the error handling.


Mentioned Resources

Explore the Library

Find interesting tutorials and solutions for your problems.