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

Hid*_*den 127 javascript async-await

之间有什么区别:

const [result1, result2] = await Promise.all([task1(), task2()]);
Run Code Online (Sandbox Code Playgroud)

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

const result1 = await t1;
const result2 = await t2;
Run Code Online (Sandbox Code Playgroud)

const [t1, t2] = [task1(), task2()];
const [result1, result2] = [await t1, await t2];
Run Code Online (Sandbox Code Playgroud)

zzz*_*Bov 155

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

  • await 是一个函数,它占用整数毫秒并返回一个在很多毫秒后解析的promise.
  • Promise.all 是一个采用整数毫秒的函数,并返回一个在很多毫秒后拒绝的promise.

呼叫res(ms)启动计时器.rej(ms)在所有延迟完成后,使用等待一些延迟将解决,但请记住它们同时执行:

示例#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
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

这意味着res将在3秒后使用来自内部承诺的数据解析.

但是,Promise.all有一个"快速失败"的行为:

例#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
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

如果您Promise.all改为使用,则必须等待每个承诺按顺序解决,这可能效率不高:

例#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
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

  • @mclzc在示例3中,进一步的代码执行将暂停,直到delay1解析。甚至在文本中,“如果您改为使用async-await,则必须等待每个诺言依次解决” (4认同)
  • “*它可能没有那么高效*” - 更重要的是,导致“unhandledrejection”错误。你永远不会想用这个。请将其添加到您的答案中。 (4认同)
  • 因此,基本上区别只是Promise.all的“快速失败”功能? (3认同)
  • 因此,关于一切都成功的成功案例,没有任何情况下 `Promise.all()` 会比背靠背执行 3 个 `await` 更快,对吗?所以为了性能原因做 `Promise.all()` 是没用的,除了快速失败的场景? (2认同)
  • @HenriLapierre,我见过太多开发人员犯了执行串行`await`s 的错误(即`data1 = await thing1(); data2 = await thing2(); data3 = await thing3();`)认为他们'重新并行运行承诺。所以回答你的问题,**如果你的承诺已经开始,他们就不能更快地解决**。我不知道为什么你会认为他们可以通过 Promise.all() 以某种方式加速。 (2认同)
  • 在示例 #3 中,所有等待完成之前的总运行时间是 3000 + 2000 + 1000 毫秒,不是吗?即,延迟 2 在延迟 1 结束时开始。 (2认同)

mik*_*kep 43

第一个区别 - 快速失败

我赞同@ zzzzBov的答案,但Promise.all的"快速失败"优势不仅仅是一个区别.评论中的一些用户询问为什么在负面情况下(当某些任务失败时)只使用Promise.all时更快.我问为什么不呢?如果我有两个独立的异步并行任务,第一个在很长一段时间内解决,但第二个在很短的时间内被拒绝,为什么让用户等待错误信息"很长时间"而不是"非常短的时间"?在现实生活中,我们必须考虑消极情景.但是好的 - 在第一个区别中,您可以决定使用Promise.all与多个等待的替代方案.

第二个区别 - 错误处理

但在考虑错误处理时,你必须使用Promise.all.无法正确处理多个await触发的异步并行任务的错误.在否定情况下,你将永远与结束UnhandledPromiseRejectionWarningPromiseRejectionHandledWarning虽然你使用try/catch语句的任何地方.这就是Promise.all的设计原因.当然有人可以说我们可以使用process.on('unhandledRejection', err => {})和抑制错误,process.on('rejectionHandled', err => {})但这不是好的做法.我在互联网上发现了许多例子,它们根本没有考虑对两个或多个独立的异步并行任务进行错误处理,或者以错误的方式考虑它 - 只是使用try/catch并希望它会捕获错误.找到好的做法几乎是不可能的.这就是我写这个答案的原因.

摘要

永远不要使用多个await来执行两个或更多个独立的异步并行任务,因为您将无法认真处理错误.始终对此用例使用Promise.all(). Async/await不是Promises的替代品.这是如何使用promises的漂亮方式...异步代码是以同步方式编写的,我们可以避免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)
  });
};
Run Code Online (Sandbox Code Playgroud)

在正面场景中运行任务时,Promise.all和多个await之间没有区别.两个例子都Task 1 succeed! Task 2 succeed!在5秒后结束.

// 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!
Run Code Online (Sandbox Code Playgroud)
// 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!
Run Code Online (Sandbox Code Playgroud)

当第一个任务在正面情况下需要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!
Run Code Online (Sandbox Code Playgroud)
// 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!
Run Code Online (Sandbox Code Playgroud)

我们应该已经注意到,当并行使用多个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 Code Online (Sandbox Code Playgroud)

正如您所看到的那样,为了成功处理错误,我们只需要为run函数添加一个catch,而catch 函数的代码就是回调(异步样式).我们不需要在run函数内部处理错误,因为它自动执行异步函数 - 承诺拒绝task函数会导致函数拒绝run.为了避免回调,我们可以使用同步样式(async/await + try/catch)try { await run(); } catch(err) { }但在这个例子中它是不可能的,因为我们不能await在主线程中使用- 它只能在异步函数中使用(它是合乎逻辑的,因为没有人想要阻止主线程).为了测试是否在处理作品同步的风格,我们可以调用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);
};
Run Code Online (Sandbox Code Playgroud)

我们可以尝试以上几种方式处理代码......

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 Code Online (Sandbox Code Playgroud)

...没有被捕获,因为它处理同步代码但是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)
Run Code Online (Sandbox Code Playgroud)

...... 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)
Run Code Online (Sandbox Code Playgroud)

......和上面一样.

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)
Run Code Online (Sandbox Code Playgroud)

......"只有"两个错误(第三个错过)但没有抓到.


添加(单独处理任务错误以及首次失败错误)

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!
Run Code Online (Sandbox Code Playgroud)

...请注意,在此示例中,我对两个任务使用了negativeScenario = true,以便更好地演示发生的情况(function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }用于触发最终错误)

  • 此答案优于接受的答案,因为当前接受的答案错过了错误处理的非常重要的主题 (6认同)
  • 无需提及线程,一切都在单个线程中运行。[并发不是并行](https://www.youtube.com/watch?v=oV9rvDllKEg) (2认同)

Gav*_*son 29

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

下面的test1test2函数显示了如何await运行异步或同步。

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

带有定时结果的 jsfiddle - 打开浏览器控制台查看测试结果

同步行为。不并行运行,需要约1800 毫秒

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
};
Run Code Online (Sandbox Code Playgroud)

异步行为。并行运行,大约需要600 毫秒

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
}
Run Code Online (Sandbox Code Playgroud)

异步行为。并行运行,大约需要600 毫秒

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

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

  • 我在哪里可以获得片段 1 和 2 中幕后发生的事情的详细解释?我很惊讶这些有不同的运行方式,因为我期望行为是相同的。 (6认同)
  • @Gregordy 是的,这很令人惊讶。我发布这个答案是为了让刚接触异步的程序员免于一些头痛。这一切都与 JS 何时评估等待有关,这就是为什么分配变量的方式很重要。深入异步阅读:https://blog.bitsrc.io/understanding-javascript-async-and-await-with-examples-a010b03926ea (3认同)

zpr*_*zpr 5

您可以自己检查。

在这个小提琴中,我进行了一个测试,以证明的阻止性质,与之await相反Promise.all,它将开始所有的承诺,而在一个等待时它将与其他承诺继续进行。

  • 实际上,您的小提琴没有解决他的问题。调用`t1 = task1()是有区别的;t2 = task2()`和** then **然后对它们两个都使用`await``result1 = await t1; result2 = await t2;`就像他的问题一样,而不是您正在测试的是在原始调用上使用`await`的东西,例如`result1 = await task1();。result2 =等待task2();`。他的问题中的代码确实立即启动了所有承诺。就像答案显示的那样,区别在于使用Promise.all方法可以更快地报告故障。 (4认同)