Await Promise.all ()과 다중 대기의 차이점은 무엇입니까?


181

다음과 같은 차이점이 있습니까?

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];

답변:


210

참고 :

이 답변 await은 시리즈와 사이의 타이밍 차이 만 다룹니다 Promise.all. 오류 처리의 더 중요한 차이점을 다루는 @mikep의 포괄적 인 답변 을 읽으십시오 .


이 답변의 목적을 위해 몇 가지 예제 방법을 사용합니다.

  • res(ms) 은 밀리 초의 정수를 취하고 그 밀리 초 후에 해결되는 약속을 반환하는 함수입니다.
  • rej(ms) 은 밀리 초의 정수를 취하고 그 밀리 초 후에 거부하는 약속을 반환하는 함수입니다.

호출 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

이는 Promise.all3 초 후에 내부 약속의 데이터로 해결 될 것임을 의미합니다 .

그러나 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

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


4
기본적으로 차이점은 Promise.all의 "실패"기능입니다.
Matthew

4
@mclzc 예제 # 3에서 delay1이 해결 될 때까지 추가 코드 실행이 중지됩니다. "동시에 async-await를 사용하는 경우 각 약속이 순차적으로 해결 될 때까지 기다려야합니다"라는 텍스트에도 있습니다.
haggis

1
@ Qback에는 동작을 보여주는 라이브 코드 스 니펫이 있습니다. 실행하고 코드를 다시 읽으십시오. 약속의 순서를 오해 한 사람은 처음이 아닙니다. 데모에서 실수 한 것은 약속을 동시에 시작하지 않는다는 것입니다.
zzzzBov 2016

1
@zzzzBov 당신이 맞아요. 당신은 동시에 시작하고 있습니다. 죄송합니다. 다른 이유로이 질문을했는데 간과했습니다.
Qback

2
" 효율적이지 않을 수 있습니다 "-더 중요한 것은 unhandledrejection오류를 일으 킵니다 . 당신은 이것을 사용하고 싶지 않을 것입니다. 이것을 답변에 추가하십시오.
Bergi

88

첫 번째 차이점-빨리 실패

@zzzzBov의 답변에 동의하지만 Promise.all의 "빠른 실패"이점은 한 가지 차이점이 아닙니다. 일부 사용자는 부정적인 시나리오에서 Promise.all이 더 빠를 때 (일부 작업이 실패한 경우) 왜 사용하는지 묻습니다. 그리고 왜 안 물어? 두 개의 독립적 인 비동기 병렬 작업이 있고 첫 번째 작업이 매우 오랜 시간에 해결되었지만 두 번째 작업이 매우 짧은 시간에 거부되면 사용자가 "매우 짧은 시간"대신 "매우 긴 시간"오류 메시지를 기다리는 이유는 무엇입니까? 실제 응용 프로그램에서는 부정적인 시나리오를 고려해야합니다. 그러나 첫 번째 차이점에서 Promise.all과 다중 대기를 사용할 대체 방법을 결정할 수 있습니다.

두 번째 차이점-오류 처리

그러나 오류 처리를 고려할 때는 반드시 Promise.all을 사용해야합니다. 다중 대기로 트리거 된 비동기 병렬 태스크의 오류를 올바르게 처리 할 수 ​​없습니다. 부정적인 시나리오에서 당신은 항상로 끝납니다 UnhandledPromiseRejectionWarning그리고 PromiseRejectionHandledWarning당신이 시도 / 캐치 어디를 사용하지만. 이것이 Promise.all이 설계된 이유입니다. 물론 누군가는 우리가 사용하여 해당 오류를 억제 할 수 있다고 말할 수 process.on('unhandledRejection', err => {})process.on('rejectionHandled', err => {})하지만 좋은 방법이 아닙니다. 인터넷에서 두 개 이상의 독립적 인 비동기 병렬 작업에 대한 오류 처리를 고려하지 않거나 전혀 고려하지 않지만 잘못된 방법으로 시도하는 많은 예제를 찾았습니다 .try / catch를 사용하고 오류를 잡기를 바랍니다. 좋은 습관을 찾는 것은 거의 불가능합니다. 이것이 제가이 답변을 쓰는 ​​이유입니다.

요약

오류를 심각하게 처리 할 수 ​​없기 때문에 둘 이상의 독립적 인 비동기 병렬 작업에는 다중 대기를 사용하지 마십시오. 이 사용 사례에는 항상 Promise.all ()을 사용하십시오. Async / await는 약속을 대체하지 않습니다. 그것은 약속을 사용하는 방법과 매우 똑같습니다 ... 비동기 코드는 동기화 스타일 로 작성 되며 우리는 여러 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과 여러 대기간에 차이가 없습니다. 두 예제 모두 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!
// 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!

우리는 이미 여러 대기를 동시에 사용할 때 잘못된 일을하고 있음을 주목해야합니다. 물론 오류를 피하려면 처리해야합니다! 해보자...


// 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 논리가있는 코드가 콜백 ( async style )에 있습니다. 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);
};

우리는 여러 가지 방법으로 코드를 처리하려고 시도 할 수 있습니다 ...

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)

... 위와 동일합니다. 삭제 된 답변의 @ Qwerty 사용자는 잡히는 것처럼 보이지만 처리되지 않은 오류가있는이 이상한 행동에 대해 물었습니다. run ()이 await 키워드와 함께 거부되어 run ()을 호출 할 때 try / catch를 사용하여 잡을 수 있기 때문에 오류가 발생합니다. 비동기 태스크 함수를 동기식으로 호출하고 (대기 키워드없이)이 태스크는 run () 함수 외부에서 실행되며 외부에서도 실패하기 때문에 처리되지 않은 오류가 발생합니다. setTimeout ...에서 코드의 일부를 실행하는 동기화 함수를 호출 할 때 try / catch로 오류를 처리 할 수없는 경우와 비슷 function test() { setTimeout(function() { console.log(causesError); }, 0); }; try { test(); } catch(e) { /* this will never catch error */ }합니다.

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)

... "오직"두 오류 (3 번째 오류가 누락 됨)이지만 아무것도 발견되지 않았습니다.


추가 (작업 오류를 별도로 처리하고 첫 번째 오류도 처리)

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사용하여 발생하는 상황을보다 잘 보여줍니다 ( 최종 오류를 발생시키는 데 사용됨).


14
이 답변은 현재 수락 된 답변이 오류 처리의 매우 중요한 주제를 놓치고 있기 때문에 수락 된 답변보다 낫습니다
chrishiestand

8

일반적으로 사용하면 Promise.all()"비동기"요청이 병렬로 실행됩니다. 사용하여 await병렬로 실행할 수 있습니다 또는 차단 "동기화"합니다.

아래 test1test2 함수는 await비동기 또는 동기화를 실행 하는 방법 을 보여줍니다 .

test3Promise.all() 은 비동기임을 보여줍니다 .

시간 결과가있는 jsfiddle- 브라우저 콘솔을 열어 테스트 결과를 봅니다.

동기화 동작. 병렬로 실행되지 않으며 ~ 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()하는 경우 "빠른 실패"도 포함됩니다. 포함 된 기능 중 첫 번째 오류 발생시 실행을 중지 하십시오.


1
스 니펫 1과 2의 후드에서 발생하는 상황에 대한 자세한 설명은 어디서 얻을 수 있습니까? 나는 행동이 동일하다고 기대하면서 이것들이 다른 방식으로 실행된다는 것에 놀랐습니다.
Gregordy

2
@Gregordy 네, 놀랍습니다. 나는이 두통을 비동기 적으로 새로운 코더를 저장하기 위해이 답변을 게시했습니다. JS가 대기를 평가할 때가 중요합니다. 이것이 변수를 할당하는 방법이 중요한 이유입니다. 깊이있는 비동기식 읽기 : blog.bitsrc.io/…
GavinBelson

7

스스로 확인할 수 있습니다.

바이올린 에서 나는 모든 약속을 시작하는 await것과는 대조적으로 의 차단 특성을 입증하기 위해 테스트를 실행했으며 , Promise.all기다리는 동안 다른 약속과 함께 진행될 것입니다.


6
실제로, 당신의 바이올린은 그의 질문을 다루지 않습니다. 호출 사이에 차이가 t1 = task1(); t2 = task2()다음 사용하여 await그들 모두 이후에 result1 = await t1; result2 = await t2;무엇을 사용하고있는 거 테스트는 달리, 자신의 질문처럼 await같은 원래의 통화는 result1 = await task1(); result2 = await task2();. 그의 질문에있는 코드는 한 번에 모든 약속을 시작합니다. 답변에서 볼 수 있듯이 차이점은 실패가 더 빨리보고된다는 것 Promise.all입니다.
BryanGrezeszak

귀하의 답변은 @BryanGrezeszak과 같은 주제와 다릅니다. 오해의 소지가있는 사용자를 피하려면 오히려 삭제해야합니다.
mikep 2018 년

0

Promise.all ([task1 (), task2 ()]);기다리는 경우 "task1 ()"및 "task2 ()"는 병렬로 실행되며 두 약속이 완료 될 때까지 (해결되거나 거부 될 때까지) 대기합니다. 반면에

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

t2는 t1이 실행을 완료 한 후 (해결되거나 거부 된)에만 실행됩니다. t1과 t2는 병렬로 실행되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.