我有一个将数据写入mongodb的函数,如下所示:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
dataKeys.forEach(async key => db.collection(key).insertMany(data[key]))
}
如果我在节点脚本中运行它,则可以正常工作。但是,当我尝试在Jest的beforeAll
中使用它时,我从Jest得到了这个异步错误:
测试运行一秒钟后,Jest没有退出。这个 通常意味着存在一些异步操作 在测试中停止了。
经过一些故障排除后,我发现是forEach
引起了麻烦。使用for循环解决了此问题:
const writeToDB = async (db, data) => {
const dataKeys = Object.keys(data)
for (const key of dataKeys) {
await db.collection(key).insertMany(data[key])
}
}
在搜索此问题时,我遇到了这篇文章: https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
那里的解释很合理,但它给我留下了一些问题:
修改
阅读所有评论后,我意识到我的第一个问题有点胡说。通常,我将异步函数的结果分配给一个变量,如果我不等待,则会出现不确定的错误。但这不是这种情况,因此脚本可以正常退出,并且数据库写入会在后台同时发生。
答案 0 :(得分:3)
即使在没有await
或未对其调用.then()
的上下文中,异步功能也可以工作,也就是说,我绝对可以做到这一点:
async function foo() {
// async calls here
}
foo(); // no await or .then().
这意味着您不能等待操作完成,不能使用该值,最糟糕的是,您不能捕获或恢复任何可能引发(或拒绝的异步错误)的错误,如果我们认为准确的话)
主要区别在于.forEach()
不在乎或等待操作完成之后再调用下一个操作(因为立即使用异步功能return
),而您的for..of
调用使用await
等待每个操作完成,然后再继续执行下一个操作。
如果您从循环内的调用中删除了.forEach
,那么您的第一个await
示例将大致等于最底层的示例。
结果是您的第一个示例返回一个Promise,该Promise立即解决,而不是在所有DB调用完成后立即解决,因此测试期望操作完成,但不是。在第二个示例中,您正确地await
使所有调用在异步功能完成之前完成,因此,返回Promise仅在所有调用自行解决之后才能解决。
请注意,这两个示例并不等效,因为第一个示例将一个接一个地调用insertMany,而又不等待它们完成,从而导致DB调用并行执行。
如果您想保留此行为,但仍返回等待所有操作完成的正确承诺,则应使用[].map()
而不是[].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 parallel
return await Promise.all(allPromises); // wait for them all to finish
}
答案 1 :(得分:2)
现有答案已经详细说明了为什么forEach
不应该与promises一起使用。 forEach
回调不考虑返回的承诺,并中断了承诺链。 async..await
需要与for..of
一起使用来评估承诺,或者与Promise.all
和map
一起使用来并行评估。
Jest支持promise,并期望从异步函数(it
等)返回的promise表示此函数中发生的异步过程已经结束。
一旦Jest完成测试,它将检查是否存在阻止Node退出的打开句柄。由于Jest并未返回承诺并将其链接起来,因此它们所代表的过程会阻止Jest完成测试过程。
此问题由上述错误消息表示:
测试运行一秒钟后,Jest没有退出。
这通常意味着有些异步操作不是 在测试中停止了。考虑使用--detectOpenHandles运行Jest 解决此问题。