javascript 等待VS承诺.所有

f0brbegy  于 2023-05-12  发布在  Java
关注(0)|答案(4)|浏览(200)

这两者之间有什么区别吗:

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
}

我知道在第一个代码片段中,所有的promise都是同时触发的,但我不确定第二个代码片段是否如此。for循环是否等待第一次迭代完成后才调用下一个promise?或者所有的promise都是同时触发的,循环内部的行为就像是对它们的回调?

5f0d552i

5f0d552i1#

是的,他们绝对是不同的。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是处理promise数组的唯一可行选择

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

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

wqnecbli

wqnecbli2#

当在异步迭代器上,当前迭代的计算依赖于以前的一些迭代时,就需要for await ...。如果没有依赖关系,则选择Promise.allfor await结构是设计用于异步迭代器的,尽管在示例中,您可以将其与promise数组一起使用。
请参阅javascript.info一书中的分页数据示例,其中使用了不能用Promise.all重写的异步迭代器:

(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;
    }
  }
}
fcipmucu

fcipmucu3#

正如你所说的,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
希望这能帮上忙。

iaqfqrcu

iaqfqrcu4#

实际上,使用for await语法确实会一次触发所有promise。
下面的代码证明了这一点:

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);
})();

stdout:time: 4.001
但是循环内部并不充当回调函数。如果我反转数组,所有日志会同时出现。我假设Promise会立即被触发,运行时只需要等待第一个Promise解决,然后进入下一次迭代。
编辑:实际上,使用for await是不好的做法,当我们使用它与异步迭代器以外的东西,最好是使用Promise.all,根据@Bergi在他的回答。

相关问题