等待VS Promise.all

时间:2020-01-11 12:17:00

标签: javascript node.js promise async-await for-await

这之间有什么区别吗?

const promises = await Promise.all(items.map(e => somethingAsync(e)));
for (const res of promises) {
  // do some calculations
}

这吗?

for await (const res of items.map(e => somethingAsync(e))) {
  // do some calculations
}

我知道在第一个代码段中,所有的诺言都在同一时间执行,但是我不确定第二个。 for循环是否等待第一次迭代完成以调用下一个promise?还是所有的诺言都在同一时间触发,循环的内部就像它们的回调一样?

4 个答案:

答案 0 :(得分:9)

是的,它们绝对不同。 for await应该用于异步迭代器,而不是用于预先存在的promise数组。

只是要弄清楚,

for await (const res of items.map(e => somethingAsync(e))) …

相同
const promises = items.map(e => somethingAsync(e));
for await (const res of promises) …

const promises = [somethingAsync(items[0]), somethingAsync(items[1]), …);
for await (const res of promises) …

somethingAsync调用在等待任何事情之前立即立即发生。然后,它们await一个接一个地被处理,如果其中任何一个被拒绝,这绝对是一个问题:这将导致未处理的promise拒绝错误。 使用Promise.all是兑现承诺的唯一可行选择

for (const res of await Promise.all(promises)) …

有关详细信息,请参见Waiting for more than one concurrent await operationAny difference between await Promise.all() and multiple await?

答案 1 :(得分:8)

当在异步迭代器上当前迭代的计算依赖于之前的一些迭代时,就需要 for await ...。如果没有依赖关系,Promise.all 是您的选择。 for await 构造旨在与异步迭代器一起使用,尽管 - 在您的示例中,您可以将它与一系列承诺一起使用。

有关使用无法使用 Promise.all 重写的异步迭代器的示例,请参阅 paginated data 书中的示例 javascript.info

(async () => {
  for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {
    console.log(commit.author.login);
  }
})();

此处 fetchCommits 异步迭代器向 fetch GitHub 存储库的提交发出请求。 fetch 以 30 次提交的 JSON 进行响应,并在 Link 标头中提供指向下一页的链接。 因此只有在上一次迭代具有下一个请求的链接后才能开始下一次迭代

async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`;

  while (url) {
    const response = await fetch(url, { 
      headers: {'User-Agent': 'Our script'}, 
    });

    const body = await response.json(); // (array of commits

    // The URL of the next page is in the headers, extract it using a regexp
    let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/);
    nextPage = nextPage?.[1];

    url = nextPage;

    for(let commit of body) { // yield commits one by one, until the page ends
      yield commit;
    }
  }
}

答案 2 :(得分:2)

如您所说Promise.all将一次性发送所有请求,然后在所有请求完成后得到响应。

在第二种情况下,您将一次性发送请求,但每个请求都会收到一个响应。

请参阅此小示例以供参考。

let i = 1;
function somethingAsync(time) {
  console.log("fired");
  return delay(time).then(() => Promise.resolve(i++));
}
const items = [1000, 2000, 3000, 4000];

function delay(time) {
  return new Promise((resolve) => { 
      setTimeout(resolve, time)
  });
}

(async() => {
  console.time("first way");
  const promises = await Promise.all(items.map(e => somethingAsync(e)));
  for (const res of promises) {
    console.log(res);
  }
  console.timeEnd("first way");

  i=1; //reset counter
  console.time("second way");
  for await (const res of items.map(e => somethingAsync(e))) {
    // do some calculations
    console.log(res);
  }
  console.timeEnd("second way");
})();

您也可以在这里尝试-https://repl.it/repls/SuddenUselessAnalyst

希望这会有所帮助。

答案 3 :(得分:0)

实际上,使用for await语法确实可以一次全部兑现承诺。

一小段代码证明了这一点:

const sleep = s => {
  return new Promise(resolve => {
    setTimeout(resolve, s * 1000);
  });
}

const somethingAsync = async t => {
  await sleep(t);
  return t;
}

(async () => {
  const items = [1, 2, 3, 4];
  const now = Date.now();
  for await (const res of items.map(e => somethingAsync(e))) {
    console.log(res);
  }
  console.log("time: ", (Date.now() - now) / 1000);
})();

标准输出: time: 4.001

但是循环的内部并不充当“回调”。如果我反转阵列,则所有日志会一次出现。我想立即兑现承诺,并且运行时仅等待第一个解决以进行下一次迭代。

编辑:实际上,当我们将for await与异步迭代器以外的其他东西一起使用时,这是个坏习惯,根据@Bergi的回答,最好是使用Promise.all