提问者:小点点

正在等待多个并发的await操作


如何更改下面的代码,使两个异步操作都被触发,并有机会并发运行?

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

共3个答案

匿名用户

不要在问题中使用这样的句型:你得到了承诺,然后单独等待它们;相反,使用:

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

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

您的解决方案并行运行它们,但总是在等待第二个之前等待第一个完成。如果您只想启动它们,并行运行它们,并得到两个结果,这很好。/s>(不,不是,继续读。。。)请注意,如果第一个代码需要(比方说)五秒钟才能完成,而第二个代码在一秒钟内失败,那么您的代码将在失败之前等待整整五秒钟。

遗憾的是,当前没有语法来执行并行等待,因此出现了您所列出的尴尬情况,即。(曾经讨论过或类似的问题,不过,也许会有一段时间。)

版本为:

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

。。。这更简洁,而且如果第一个操作很快失败,也不会等待第一个操作完成(例如,在我上面的五秒/一秒的例子中,上面的操作会在一秒内拒绝,而不是等待五秒)。还要注意的是,在您的原始代码中,如果第二个承诺在第一个承诺解决之前被拒绝,那么您很可能会在控制台中得到一个“未处理的拒绝”错误(当前使用Chrome V61确实如此;更新:更新后的版本有更有趣的行为),尽管该错误可以说是虚假的(因为您最终确实处理了拒绝,因为该代码显然是在一个函数中,因此该函数会将拒绝挂钩,并使其承诺与之一起被拒绝)(更新:再次更改)。但是如果两个承诺都拒绝,您将得到一个真正的未处理的拒绝错误,因为控制流从未达到,因此p2拒绝从未被处理。

未处理的拒绝是一件坏事(以至于很快,Node.js就会对真正未处理的拒绝中止处理,就像未处理的异常一样,因为它们就是这样),所以最好避免在问题中使用“Get the promise thenit”模式。

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

null

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.

匿名用户

我想这应该管用:

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

下面是一个更详细的例子,以帮助理解:

null

const promise1 = async() => {
  return 3;
}

const promise2 = async() => {
  return 42;
}

const promise3 = async() => {
  return 500;
  // emulate an error
  // throw "something went wrong...";
}

const f1 = async() => {

  try {
    // returns an array of values
    const results = await Promise.all([promise1(), promise2(), promise3()]);
    console.log(results);
    console.log(results[0]);
    console.log(results[1]);
    console.log(results[2]);

    // assigns values to individual variables through 'array destructuring'
    const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);

    console.log(value1);
    console.log(value2);
    console.log(value3);

  } catch (err) {
    console.log("there was an error: " + err);
  }

}

f1();

匿名用户

确保您正确地处理了拒绝,并且您可以安全地使用promises.all()而不会面临未处理的拒绝。(编辑:根据讨论进行澄清:不是错误,而是代码未处理的拒绝。将引发第一个promise拒绝,并忽略rest)。

在下面的示例中,返回了一个数组[error,results],..],以便于处理结果和/或错误。

null

let myTimeout = (ms, is_ok) =>
  new Promise((resolve, reject) => 
    setTimeout(_=> is_ok ? 
                   resolve(`ok in ${ms}`) :
                   reject(`error in ${ms}`),
               ms));

let handleRejection = promise => promise
  .then((...r) => [null, ...r])
  .catch(e => [e]); 

(async _=> {
  let res = await Promise.all([
    myTimeout(100, true),
    myTimeout(200, false),
    myTimeout(300, true),
    myTimeout(400, false)
  ].map(handleRejection));
  console.log(res);
})();