问题描述
我有一个 nodejs 函数 processReviews(workflow)
,当调用它时,应该将多个 promise 推送到数组 promises[]
,然后在 for 循环后使用 promises.all()
运行它们。
function examplePromiseFunc(){
return new Promise((resolve,reject) => {
console.log("examplePromiseFunc() INSIDE ")
resolve('done')
})
}
async function processReviews(workflow){
//get objects from s3
let allObjects = await getAllObjects(workflow);
allObjects = allObjects.filter(obj => obj.Key.includes('output.json'))
console.log(`found ${allObjects.length} .json files.`)
const promises = [];
for (let i = 0; i < allObjects.length; i++) {
console.log('i=',i,' pushing to promises[]')
promises.push( examplePromiseFunc() )
}
const result = await Promise.all(promises)
console.log('running,result = ',result);
}
但是当我运行我的代码时,输出看起来像这样:
found 697 .json files.
i= 0 pushing to promises[]
examplePromiseFunc() INSIDE
i= 1 pushing to promises[]
examplePromiseFunc() INSIDE
i= 2 pushing to promises[]
examplePromiseFunc() INSIDE
i= 3 pushing to promises[]
examplePromiseFunc() INSIDE
...
这意味着每次我向 promises[] 数组 (promises.push( await examplePromiseFunc() )
) 推送一个 promise 时,函数 examplePromiseFunc()
都会立即被调用并且不会等待。
我希望我的函数只在最后运行 await Promise.all(promises)
时才被调用,有什么我遗漏的吗?我的异步函数会导致问题吗?我一直在阅读 javascript promises.all,这似乎是一个很好的实现。
解决方法
问题是您已经在循环中使用了 await
,这意味着循环将“等待”并按顺序处理项目。
相反,您应该将承诺添加到数组中,然后像您一样在最后 await
所有:
async function processReviews(workflow) {
//get objects from s3
const allObjects = await getAllObjects(workflow);
const promises = [];
for (let i = 0; i < allObjects.length; i++) {
// Don't await the promise here,just start it and add it to the array.
promises.push(examplePromiseFunc(allObjects[i]));
}
const result = await Promise.all(promises)
console.log(result);
}
,
这里对 Promise
构造函数的工作原理存在一个根本性的误解。
构造函数接受一个称为 executor
的函数参数:
new Promise( executor)
执行器函数在构造过程中同步调用,带有两个通常称为resolve
和reject
的函数参数:
executor( resolve,reject)
promise executor 负责启动异步操作(通常)并使 resolve
和 reject
函数可用于处理操作完成和错误处理的代码,通常在回调函数中。
因此代码
for (let i = 0; i < allObjects.length; i++) {
console.log('i=',i,' pushing to promises[]')
promises.push( examplePromiseFunc() )
}
多次调用 examplePromiseFunct
并且在该函数内,返回的承诺的执行器在构造期间被同步调用(通过 Promise
)。因此,日志正如人们所期望的:每次调用 examplePromiseFunc
时都会记录“examplePromiseFunc() INSIDE”的日志。
这个误会很可能会导致第二个误会:
Promise.all
不“运行”承诺 - 承诺是被动对象,通过调用附加到他们 - 或者如果用承诺解决,则链接另一个承诺。
resolve
简单地返回一个承诺,该承诺由其参数承诺的已履行结果数组完成,或者被拒绝原因或参数数组中第一个被拒绝的承诺。它在本机代码中有一个执行程序,可以有效地将 reject
处理程序附加到其参数数组中的承诺,然后被动等待(即返回到事件循环),直到一次解决一个参数承诺。
那是因为 Promise 只能以这种方式工作。当您将承诺推入数组时,您已经在等待它(在循环内),即如果您不等待它们执行,那么它们也会执行,有或没有 await promise.all,也有可能所有的 Promise 在你在 promise.all 中传递那个数组之前已经解决了。 下面的函数也将在没有全部承诺的情况下解决所有承诺。
async function processReviews(workflow){
//get objects from s3
let allObjects = await getAllObjects(workflow);
allObjects = allObjects.filter(obj => obj.Key.includes('output.json'))
console.log(`found ${allObjects.length} .json files.`)
const promises = [];
for (let i = 0; i < allObjects.length; i++) {
console.log('i=',' pushing to promises[]')
promises.push( examplePromiseFunc() )
}
}
此外,您不应该无限制地使用 promise.all,因为它可能会达到您的硬件限制。 使用有限制的地图可以减少您的问题。
async function processReviews(workflow) {
//get objects from s3
let allObjects = await getAllObjects(workflow);
allObjects = allObjects.filter(obj => obj.Key.includes("output.json"));
console.log(`found ${allObjects.length} .json files.`);
for (let i = 0; i < allObjects.length; i = i + PROMISE_LIMIT) {
const objects = allObjects.slice(i,i + PROMISE_LIMIT);
const result = await Promise.all(objects.map(elem => examplePromiseFunc()));
console.log("running,result = ",result);
}
}