为什么Array.map Array.forEach异步操作需要Promise.all?

时间:2020-05-09 13:59:35

标签: javascript node.js

我正在使用NodeJS(ExpressJS)服务器,具有以下代码:

let caseScore = 0
await questions.map(async q => {
  caseScore += await q.grade() // Queries database and gets questions, finds score
}))
console.log(caseScore)
>> Output: 0

但是,似乎在请求完成之后q.grade()完成了执行。我将console.log()放在q.grade中,它在发送响应后显示。显然,这是异步执行的。所以后来我这样做了:

let caseScore = 0
await Promise.all(questions.map(async q => {
  caseScore += await q.grade() // Queries database and gets questions, finds score
})))
console.log(caseScore)
>> Output: 2

它完美地工作。有人可以向我解释为什么需要Promise.all吗?另外,如果我将.map替换为.forEach,Promise.all错误,那么对.forEach进行此更改的正确方法是什么?

3 个答案:

答案 0 :(得分:4)

您应该等待所有异步调用完成。这就是需要 promise.all 的原因。

它将承诺集合转换为单个承诺。

答案 1 :(得分:2)

Array.prototype.map就是这样。

Array.prototype.map = function(callback) {
  var results = [];
  for (var i = 0; i < this.length; i++) {
    results.push(callback(this[i], i));
  }
  return result;
}

在您的代码中,回调函数返回Promise,而Array.map返回数组Promise。因为它不等待回调完成。 Array.forEach也不会为您服务,因为它也不会等待回调。

解决问题的最佳方法是使用Promise.allPromise的数组转换为单个Promise并等待它。

await Promise.all(questions.map(async q => {
  caseScore += await q.grade();
}));

或者像这样使用简单的for循环。

for (var q of questions) {
  caseScore += await q.grade();
}

答案 2 :(得分:2)

当您await放置一个诺言数组时,您将等待一个不是诺言的数组(即使其中包含诺言)。 await将立即解析不是Promise的值。就像您做了await 'hello'一样,那将立即解决。

Promise.all是一个实用程序,它公开一个新的Promise,只有当作为参数传递的数组中的所有promise都被解析时,它才会解析。由于它是自己的承诺,因此您可以在awaitPromise.all

[编辑] 小心,不要在这样的循环中使用await

for (var q of questions) {
  caseScore += await q.grade();
}

原因是它翻译为

questions[0].grade().then(score => 
  return questions[1].grade().then(score => 
    return questions[2].grade().then(score => 
      return questions[3].grade().then(score => 
        // ...
      );
    );
  );
);

它在内存中创建了很多上下文,并使所有串行内容都没有利用javascript异步性质

如果您希望在Promise之间共享总和或某些值,则可以使用上下文对象

async function grade1() {
  this.sum += await getSumFromServer();
  this.nbOfTasks += 1;
}

async function grade2() {
  this.sum += await getSumFromServer();
  this.nbOfTasks += 1;
}

async function grade3() {
  this.sum += await getSumFromServer();
  this.nbOfTasks += 1;
}

async function main() {
  const context = {
    sum: 0,
    nbOfTasks: 0,
  }
  
  const promisesWithContext = [grade1, grade2, grade3]
    .map(fn => fn.call(context));

  await Promise.all(promisesWithContext);
  
  console.log(context);
}

main();

// STUB
function getSumFromServer() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(Math.random() * 100)
    }, 1000)
  });
}