一尘不染

等待多个 并发 等待操作

javascript

我如何更改以下代码,以触发两个异步操作并有机会同时运行?

const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values

我需要做这样的事情吗?

const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values

阅读 562

收藏
2020-07-09

共1个答案

一尘不染

TL; DR
不要在获得承诺的问题中使用模式,而是分别等待它们;而是使用Promise.all(至少现在):

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

虽然您的解决方案确实并行运行这两个操作,但是如果两个诺言都被拒绝,它就无法正确处理拒绝。

细节:
您的解决方案并行运行它们,但始终等待第一个完成,然后再等待第二个。如果您只想启动它们,并行运行它们,并获得两个结果,那就很好了。 (不,不是,请继续阅读…)请注意,如果第一个(例如)完成五秒钟,而第二个在一秒钟内失败,则您的代码将等待整整五秒钟,然后再失败。

遗憾的是,目前没有await并行等待的语法,因此您有列出的尴尬之处,或者Promise.all。(不过,曾经有过await.all类似的讨论;也许有一天。)

Promise.all版本是:

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

…更简洁,如果第二个操作很快失败,也不会等待第一个操作完成(例如,在我的五秒钟/一秒钟的示例中,上面的操作将在一秒钟内拒绝而不是等待五个) 。另请注意,使用原始代码,如果第二个承诺在第一个承诺解决之前被拒绝,您很可能在控制台中收到“未处理的拒绝”错误(您目前使用Chrome v61;更新:更新的版本具有更有趣的行为) ,尽管该错误可以说是虚假的(因为您这样做,最终可以处理拒绝,因为该代码显然在async函数¹中,因此该函数将钩住拒绝并使其承诺随其拒绝)(更新:再次,更改)。但是,如果两个诺言都被拒绝,您将得到真正的未处理的拒绝错误,因为控制流永远不会到达const value2 = await p2;,因此永远不会处理p2拒绝。

未处理的拒绝是Bad Thing™(以至于很快,Node.js会在真正未处理的拒绝上终止流程,就像未处理的异常一样-因为它们就是这样),因此最好避免“先得到承诺await”。问题中的模式。

这是一个故障情况下时间差异的示例(使用500ms和100ms,而不是5秒和1秒),还可能是虚假的未处理拒绝错误(打开真实的浏览器控制台查看):

const getValue1Async = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, "value1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error");
  });
};

// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
  try {
    console.time("separate");
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    const value1 = await p1;
    const value2 = await p2;
  } catch (e) {
    console.error(e);
  }
  console.timeEnd("separate");
})();

// This fails after just 100ms, because it doesn't wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
  try {
    console.time("Promise.all");
    const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
  } catch (e) {
    console.timeEnd("Promise.all", e);
  }
}, 1000);
Open the real browser console to see the unhandled rejection error.

在这里,我们同时拒绝p1和p2,从而导致以下错误p2:

在评论中,您询问:

附带问题:以下部队是否会同时等待(并丢弃结果)await p1 && await p2?

关于承诺拒绝的问题与您的原始代码有相同的问题:p1即使p2更早拒绝,它也会等到解决为止。如果在解决之前拒绝,它可能会产生一个可能是虚假的(update: 或临时)未处理的拒绝错误;并且如果和都拒绝,则会产生真正的未处理的拒绝错误(因为从未处理过拒绝)。p2p1p1p2p2

p1解决和p2拒绝的情况如下:

…而且两者都拒绝:

¹ “ …此代码显然在async函数中…”在2017年编写此问题和答案时确实如此。从那以后,高层await发生了/正在发生。

2020-07-09