Skip to content Skip to sidebar Skip to footer

Using Foreach And Async/await, Behaves Different For Node And Jest

I have a function that writes data to a mongodb, like so: const writeToDB = async (db, data) => { const dataKeys = Object.keys(data) dataKeys.forEach(async key => db.coll

Solution 1:

The existing answer already explains in detail why forEach shouldn't be used with promises the way it's used. forEach callback doesn't take returned promises into account and breaks promise chain. async..await needs to be used with for..of to evaluate promises in series or with Promise.all and map to evaluate in parallel.

Jest supports promises and expects that a promise that is returned from asynchronous function (it, etc) signifies that asynchronous process that occurred in this function has ended.

Once Jest finishes a test, it checks if there are open handles that prevent Node from exiting. Since promises weren't returned and chained by Jest, processes that they represent prevent Jest from finishing test process.

This problem is represented by said error message:

Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.

Solution 2:

Async functions work even in contexts that don't await or call .then() on them, that is, I can definitely do this:

async function foo() {
  // async calls here
}

foo(); // no await or .then().

This means you cannot wait for the operation to finish, you can't use the value, and worst of all, you cannot catch or recover from any async errors that might be thrown (or rejected, if we're being accurate)

The main difference is that the .forEach() doesn't care or wait for the operations to finish before calling the next one (since async functions return immediately), whereas your for..of call uses await to wait for each operation to complete before moving on to the next.

Your first .forEach example would be roughly equivalent to the bottom one if you removed the await from the call inside of the loop.

The result is that your first example returns a Promise that resolves immediately, rather than after all of your DB calls were finished, so the test expects the operations to finish, but they are not. In the second example, you properly await for all the calls to finish before the async function finishes, so the return Promise will only resolve after all of the calls resolve themselves, first.


On that note, the two examples are not equivalent because the first would call the insertMany one after the other without waiting for them to finish, causing the DB calls to perform in parallel.

If you want to preserve this behavior, but still return a correct Promise which waits for everything to finish, you should be using [].map() instead of [].forEach:

const writeToDB = async (db, data) => {
  const dataKeys = Object.keys(data)
  const allPromises = dataKeys.map(async key =>
    await db.collection(key).insertMany(data[key])) // run all calls in parallelreturnawaitPromise.all(allPromises); // wait for them all to finish
}

Post a Comment for "Using Foreach And Async/await, Behaves Different For Node And Jest"