等待多个并发等待操作

时间:2017-10-23 12:24:19

标签: javascript promise async-await es2017

如何更改以下代码,以便触发异步操作并同时运行?

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

4 个答案:

答案 0 :(得分:42)

TL; DR

请勿在获得承诺的问题中使用该模式,然后单独等待它们;相反,使用Promise.all(至少现在):

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

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

详细信息:

您的解决方案并行运行它们,但在等待第二个之前总是等待第一个完成。 如果您只想启动它们,并行运行它们,并获得两个结果,就可以了。 (不,它不是,继续阅读......)请注意,如果第一个完成(比方说)五秒钟而第二个事件在一秒内失败,那么您的代码将等待整整五秒钟然后失败。

可悲的是,目前没有await语法来执行并行等待,因此您有列出的尴尬或Promise.all。 (虽然有been discussion of await.all or similar;也许有一天。)

Promise.all版本是:

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

...这更简洁,并且如果第二次操作快速失败,也不等待第一次操作完成(例如,在我的五秒钟/一秒钟的例子中,上面将在一秒内拒绝而不是等五)。另请注意,使用原始代码,如果第二个承诺在第一个承诺解决之前拒绝,您可能会在控制台中出现“未处理的拒绝”错误(您当前使用的是Chrome v61),尽管该错误可能是虚假的(因为您,最终,处理拒绝)。但如果两个承诺拒绝,您将得到一个真正的未处理拒绝错误,因为控制流永远不会达到const value2 = await p2;,因此永远不会处理p2拒绝。

未处理的拒绝是一件坏事™(以至于很快,NodeJS会在真正无法处理的拒绝中止流程,就像未处理的例外 - 因为它们就是这样),所以最好避免“得到承诺然后{ {1}}它“模糊了你的问题。

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

await
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
const getValue1Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 500, "error1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error2");
  });
};

// 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.

这与您的原始代码在承诺拒绝方面存在相同的问题:即使await p1 && await p2先前拒绝,它也会等到p1结算;如果p2p2结算之前拒绝,则可能会产生可论证的虚假未处理拒绝错误;如果p1p1都拒绝(因为永远不会处理p2的拒绝),它会生成真正的未处理拒绝错误。

以下是p2结算且p1拒绝的情况:

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

(async () => {
  try {
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    console.log("waiting");
    await p1 && await p2;
  } catch (e) {
    console.error(e);
  }
  console.log("done waiting");
})();

......两者都拒绝:

Look in the real console (for the unhandled rejection error).
const getValue1Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 500, "error1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error2");
  });
};

(async () => {
  try {
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    console.log("waiting");
    await p1 && await p2;
  } catch (e) {
    console.error(e);
  }
  console.log("done waiting");
})();

答案 1 :(得分:7)

我认为这应该有效:

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

如果有助于理解,下面会有一个更详细的例子:



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




答案 2 :(得分:0)

使用.catch()和Promise.all()

确保正确处理拒绝,并且可以安全地使用Promises.all()而不遇到未处理的拒绝。 (编辑:每次讨论都需要澄清:不是错误unhandled rejection,而只是代码未处理的拒绝。Promise.all()将抛出第一个Promise拒绝,并将忽略 )。

在下面的示例中,返回[[错误,结果],...]数组,以简化处理结果和/或错误的过程。

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

您可以从catch()内抛出以停止等待所有操作(并丢弃其余结果),但是-每个try / catch块只能执行一次,因此需要维护和检查标志has_thorwn以确保不会发生未处理的错误。

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

let has_thrown = false;

let handleRejection = promise => promise
  .then((...r) => [null, ...r])
  .catch(e => {
    if (has_thrown) {
      console.log('not throwing', e);
    } else {
      has_thrown = 1;
      throw e;
    }
  });

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

答案 3 :(得分:0)

解决而不是承诺

const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)

;(async function parallel() {

  let task1 = reject(500, 'parallelTask1').catch(e).then(l)
  let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
  let task3 = reject(1500, 'parallelTask3').catch(e).then(l)

  console.log('WAITING')

  ;[task1, task2, task3] = [await task1, await task2,  await task3]

  console.log('FINISHED', task1, task2, task3)

})()

正如其他答案中指出的那样,被拒绝的承诺可能会引发未处理的异常。
这个 {.catch(e => e) 是一个精巧的小技巧,它可以捕获错误并将错误传递到链下,从而允许对resolve而不是rejecting进行承诺。

如果您发现此ES6代码很丑,请查看更友好的here