等待Promise.all()和多个等待之间的任何区别?

时间:2017-07-24 15:58:45

标签: javascript async-await

之间有什么区别:

const [result1, result2] = await Promise.all([task1(), task2()]);

const t1 = task1();
const t2 = task2();

const result1 = await t1;
const result2 = await t2;

const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];

5 个答案:

答案 0 :(得分:120)

出于本答案的目的,我将使用一些示例方法:

  • res(ms)是一个函数,它接受一个整数毫秒并返回一个在很多毫秒后解析的promise。
  • rej(ms)是一个函数,它接受一个整数毫秒并返回一个在很多毫秒后拒绝的promise。

调用res启动计时器。在所有延迟完成后,使用Promise.all等待一些延迟将解决,但请记住它们同时执行:

示例#1
const data = await Promise.all([res(3000), res(2000), res(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========O                     delay 3
//
// =============================O Promise.all



async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }

  const data = await Promise.all([res(3000), res(2000), res(1000)])
  console.log(`Promise.all finished`, Date.now() - start)
}

example()




这意味着Promise.all将在3秒后使用内部承诺中的数据解析。

但是,Promise.all has a "fail fast" behavior

示例#2
const data = await Promise.all([res(3000), res(2000), rej(1000)])
//                              ^^^^^^^^^  ^^^^^^^^^  ^^^^^^^^^
//                               delay 1    delay 2    delay 3
//
// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =========X                     Promise.all



async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const data = await Promise.all([res(3000), res(2000), rej(1000)])
  } catch (error) {
    console.log(`Promise.all finished`, Date.now() - start)
  }
}

example()




如果您使用async-await,则必须等待每个承诺按顺序解决,这可能效率不高:

示例#3
const delay1 = res(3000)
const delay2 = res(2000)
const delay3 = rej(1000)

const data1 = await delay1
const data2 = await delay2
const data3 = await delay3

// ms ------1---------2---------3
// =============================O delay 1
// ===================O           delay 2
// =========X                     delay 3
//
// =============================X await



async function example() {
  const start = Date.now()
  let i = 0
  function res(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
        console.log(`res #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  function rej(n) {
    const id = ++i
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject()
        console.log(`rej #${id} called after ${n} milliseconds`, Date.now() - start)
      }, n)
    })
  }
  
  try {
    const delay1 = res(3000)
    const delay2 = res(2000)
    const delay3 = rej(1000)

    const data1 = await delay1
    const data2 = await delay2
    const data3 = await delay3
  } catch (error) {
    console.log(`await finished`, Date.now() - start)
  }
}

example()




答案 1 :(得分:17)

第一个区别-快速失败

我同意@zzzzBov的回答,但Promise.all的“快速失败”优势不仅是一个区别。评论中的一些用户询问为什么仅在否定情况下(某些任务失败时)使用Promise.all才更快。我问为什么不呢?如果我有两个独立的异步并行任务,并且第一个在很长时间内得到解决,但第二个在很短时间内就被拒绝了,为什么要让用户等待错误消息“很长一段时间”而不是“很短时间”?在实际应用中,我们必须考虑负面情况。但是,好的-在这第一个区别中,您可以决定使用Promise.all与多次等待。

第二个区别-错误处理

但是在考虑错误处理时,您必须使用Promise.all。无法正确处理多次等待触发的异步并行任务的错误。在否定情况下,尽管您在任何地方都使用try / catch,但是您总是以UnhandledPromiseRejectionWarningPromiseRejectionHandledWarning结尾。这就是Promise.all被设计的原因。当然,有人可以说我们可以使用process.on('unhandledRejection', err => {})process.on('rejectionHandled', err => {})来消除这种错误,但这不是一个好习惯。我在互联网上发现了许多示例,这些示例根本不考虑针对两个或多个独立异步并行任务的错误处理或以错误的方式进行考虑-仅使用try / catch并希望它会捕获错误。找到好的做法几乎是不可能的。这就是为什么我要写这个答案。

摘要

不要对两个或多个独立的异步并行任务使用多次等待,因为您将无法认真处理错误。对于此用例,请始终使用Promise.all()。 异步/等待不能代替Promises。这是使用诺言的好方法...异步代码以 sync样式编写,我们可以避免在诺言中使用多个then

有人说使用Promise.all()我们不能单独处理任务错误,而只能处理第一个被拒绝的承诺的错误(是的,某些用例可能需要单独处理,例如用于日志记录)。没问题-请参阅下面的“添加”标题。

示例

考虑此异步任务...

const task = function(taskNum, seconds, negativeScenario) {
  return new Promise((resolve, reject) => {
    setTimeout(_ => {
      if (negativeScenario)
        reject(new Error('Task ' + taskNum + ' failed!'));
      else
        resolve('Task ' + taskNum + ' succeed!');
    }, seconds * 1000)
  });
};

在积极的情况下运行任务时,Promise.all和多次等待之间没有区别。两个示例都在5秒后以Task 1 succeed! Task 2 succeed!结尾。

// Promise.all alternative
const run = async function() {
  // tasks run immediate in parallel and wait for both results
  let [r1, r2] = await Promise.all([
    task(1, 5, false),
    task(2, 5, false)
  ]);
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!
// multiple await alternative
const run = async function() {
  // tasks run immediate in parallel
  let t1 = task(1, 5, false);
  let t2 = task(2, 5, false);
  // wait for both results
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: Task 1 succeed! Task 2 succeed!

在肯定的情况下,第一个任务花费10秒,在否定的情况下,秒任务花费5秒,则所发出的错误有所不同。

// Promise.all alternative
const run = async function() {
  let [r1, r2] = await Promise.all([
      task(1, 10, false),
      task(2, 5, true)
  ]);
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// multiple await alternative
const run = async function() {
  let t1 = task(1, 10, false);
  let t2 = task(2, 5, true);
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};
run();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!

我们已经在这里注意到,当并行使用多个await时,我们做错了。当然为了避免错误,我们应该处理它!让我们尝试...


// Promise.all alternative
const run = async function() {
  let [r1, r2] = await Promise.all([
    task(1, 10, false),
    task(2, 5, true)
  ]);
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: Caught error Error: Task 2 failed!

如您所见,要成功处理错误,我们只需向run函数添加一个catch即可,并且具有catch逻辑的代码处于回调(异步样式)中。我们不需要处理run函数内部的错误,因为异步函数会自动执行-答应拒绝task函数会导致拒绝run函数。为了避免回调,我们可以使用 sync样式(async / await + try / catch)try { await run(); } catch(err) { },但是在此示例中这是不可能的,因为我们不能在主线程中使用await -它只能在异步函数中使用(这是合乎逻辑的,因为没有人希望阻塞主线程)。要测试处理是否以 sync风格工作,我们可以从另一个异步函数调用run函数,或使用IIFE(立即调用函数表达式):(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();

这只是运行两个或多个异步并行任务并处理错误的正确方法。您应避免使用以下示例。


// multiple await alternative
const run = async function() {
  let t1 = task(1, 10, false);
  let t2 = task(2, 5, true);
  let r1 = await t1;
  let r2 = await t2;
  console.log(r1 + ' ' + r2);
};

我们可以尝试通过几种方式来处理代码...

try { run(); } catch(err) { console.log('Caught error', err); };
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled 

...什么也没抓住,因为它可以处理同步代码,但是run是异步的

run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

... Wtf?我们首先看到未处理任务2的错误,后来发现了该错误。令人误解,但控制台中仍然充满错误。这样无法使用。

(async function() { try { await run(); } catch(err) { console.log('Caught error', err); }; })();
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: Caught error Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

...与上面相同。

const run = async function() {
  try {
    let t1 = task(1, 10, false);
    let t2 = task(2, 5, true);
    let r1 = await t1;
    let r2 = await t2;
  }
  catch (err) {
    return new Error(err);
  }
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Caught error', err); });
// at 5th sec: UnhandledPromiseRejectionWarning: Error: Task 2 failed!
// at 10th sec: PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

...“仅”两个错误(缺少第三个错误),但没有发现任何问题。


加法(分别处理任务错误和首次失败错误)

const run = async function() {
  let [r1, r2] = await Promise.all([
    task(1, 10, true).catch(err => { console.log('Task 1 failed!'); throw err; }),
    task(2, 5, true).catch(err => { console.log('Task 2 failed!'); throw err; })
  ]);
  console.log(r1 + ' ' + r2);
};
run().catch(err => { console.log('Run failed (does not matter which task)!'); });
// at 5th sec: Task 2 failed!
// at 5th sec: Run failed (does not matter which task)!
// at 10th sec: Task 1 failed!

...请注意,在此示例中,我对两个任务都使用了negativeScenario = true,以便更好地演示会发生什么情况(throw err用于引发最终错误)

答案 2 :(得分:7)

通常,使用Promise.all()并行运行请求“异步”。使用await可以并行运行,也可以被“同步”阻止。

下面的

test1 test2 函数展示了await如何运行异步或同步。

test3 显示Promise.all()是异步的。

jsfiddle with timed results-打开浏览器控制台以查看测试结果

同步行为。不并行运行,需要〜 1800ms

const test1 = async () => {
  const delay1 = await Promise.delay(600); //runs 1st
  const delay2 = await Promise.delay(600); //waits 600 for delay1 to run
  const delay3 = await Promise.delay(600); //waits 600 more for delay2 to run
};

异步行为。并行运行,需要〜 600ms

const test2 = async () => {
  const delay1 = Promise.delay(600);
  const delay2 = Promise.delay(600);
  const delay3 = Promise.delay(600);
  const data1 = await delay1;
  const data2 = await delay2;
  const data3 = await delay3; //runs all delays simultaneously
}

异步行为。并行运行,需要〜 600ms

const test3 = async () => {
  await Promise.all([
  Promise.delay(600), 
  Promise.delay(600), 
  Promise.delay(600)]); //runs all delays simultaneously
};

TLDR;如果您使用的是Promise.all(),它也将“快速失败”-在所包含的任何功能第一次失败时停止运行。

答案 3 :(得分:4)

你可以自己检查一下。

在这个fiddle中,我进行了一次测试,以展示await的阻止性质,而不是Promise.all,这将启动所有承诺,而等待它的人将会去与其他人一起。

答案 4 :(得分:1)

等待Promise.all([task1(),task2()]); 的情况下,“ task1()”和“ task2()”将并行运行并等待,直到两个诺言都已完成(已解决或已拒绝)。而

const result1 = await t1;
const result2 = await t2;

t2仅在t1完成执行(已解决或拒绝)后运行。 t1和t2都不会并行运行。