본문 바로가기
자바스크립트(JavaScript)/자바스크립트

[JavaScript] Promise, 자바스크립트 비동기 처리기

by yerica 2024. 12. 6.
자바스크립트가 동기적으로 처리되기 때문에 비동기 처리를 위해 Promise를 사용한다는 것은 알았다.
이제, Promise를 사용하는 방법과 async, await를 왜 사용하는지에 대해 알아보자.

 

Promise란?

Promise는 비동기 작업의 성공 또는 실패한 결과를 담고있는 객체이다.

수에 콜백을 전달하는 대신에 콜백을 첨부하는 방식으로 사용되는 것이다.

 

이 비동기 함수는 실행하기 전까지 단순 값을 담고 있기 때문에 아래 세 가지 상태를 가질 수 있다.

1. 대기(Pending) :  비동기 함수가 아직 시작하지 않은 초기 상태
2. 성공(Fulfilled) : 비동기 함수가 성공적으로 완료된 상태
3. 실패(Rejected) : 비동기 함수가 실패한 상태

 

실행되기 전 Promise는 대기상태로 큐(대기열)에 저장된다.

저장된 비동기 함수는 작업이 실행됨에 따라 성공할 수도, 어떤 이유(오류)로 인해 실패할 수도 있다.

이러한 성공과 실패의 결과 값을 then이나 catch메서드를 사용해 호출하는 방식이다.

 

Promise의 가장 큰 장점 중에 하나는 바로 Chaining인데,

바로 이 then과 catch 함수를 통해 promise chain을 만들 수 있다.

출처: mdn - promise




Promise를 사용하여 비동기 처리를 할 경우 콜백함수보다 쉽고 간단히 작성 가능하며, 가독성이 높아진다.
이러한 이유로 현대의 자바스크립트의 비동기 처리는 주로 Promise를 기반으로 구현된다.

Promise 선언

const exPromise = new Promise((resolve, reject) => {
	resolve("작업 성공");
	reject("작업 실패");
}

 

Promise는 비동기 '함수'를 포함하는 '객체'이다.

일반 함수와 작성되어지는 방식은 방식은 비슷하지만 new Promise() 생성자를 통해 생성되므로 Promise를 새로운 객체를 만들고 만들어진 객체 저장하여 사용한다.

 

Promise 안에서 비동기 함수는 'resolve'와 'reject'라는 parameter(매개변수)를 가진다.

'resolve'와 'reject'는 각각 성공과 실패를 의미하는 매개변수로 프로미스 안에서 resolve()와 reject() 함수를 통해 실패했을 경우와 성공했을 경우를 지정할 수 있다.

 

위의 예제에서는 성공했을 경우 "작업 성공" 이, 실패했을 경우 "작업 실패"가 결과값으로 전달되어질 것이다.

Promise의 콜백함수

then과 catch 라는 콜백함수를 통해 결과값을 실행해 보았다.

작업이 성공한다면 then을 통해 resolve의 결과값이, 실패한다면 reject의 결과값이 호출될 것이다.

const exPromise = new Promise((resolve, reject) => {
	resolve("작업 성공");
	reject("작업 실패");
}

exPromise
	.then((result) => console.log(result); ) // 작업 성공
	.catch((errer) => console.log(errer); ) // 작업 실패

 

위의 then과 catch에서는 화살표 함수로 간단히 작성되어 졌지만, 기존 형태는 아래와 같다.

exPromise
	.then(function (result) { return console.log(result); }) // 작업 성공
	.catch(function (errer) { return console.log(errer); }) // 작업 실패

 

대기중인 Promise를 실행하기 위해선 then(), catch(), 혹은 finally()와 같은 콜백함수가 필요하다.

이러한 콜백함수를 통해 Promise는 새로운 Promise 객체를 반환한다.

Promise.prototype.then()

then()은 두 개의 콜백 함수를 인수로 받아 값을 리턴하는 메소드로, Promise가 성공 혹은 실패 했을 때를 위한 콜백 함수이다.

// function1 = 성공 시 실행될 함수 // function2 = 실패시 실행될 함수
Promise.then( function1, function2 )

 

위에서 function1과 function2라는 함수를 인수로 받아 사용했다.

작업이 성공했을 경우  then 메소드가 실행되고 Promise의 resolve된 값이 function1의 매개변수에 할당된다.

아래의 예제에서는 sucess함수가 function1함수 failed 함수가 funcion2에  해당한다.

const exPromise = new Promise((resolve, reject) => {
	resolve("작업 성공");
	reject("작업 실패");
}

// 작성 방법(1)
exPromise.then(function success(resolve){ 
	console.log(resolve)
}, function failed(reject){
	console.log(reject)
})

// 작성 방법(2)
function success(resolve){ console.log(resolve) };
function failed(reject){ console.log(reject) };
exPromise.then(success, failed);

 

작업이 성공했을 경우 then 메소드가 실행되고 reslove 값인 "작업 성공"이 then안의 success함수의 매개변수로 들어간다.

결과적으로 성공하면 console.log("작업 성공")이 실행되는 것이다. (reject도 마찬가지)

 이를 통해 우리는 then() 메소드는 반드시 매개변수가 존재해야한다는 것을 알 수 있다.
만약 매개변수가 없다면 콜백함수가 이전의 promise의 결과를 받지 못한다.

 

Chaining

이제 체이닝(Chaining)에 대해 알아보겠다.

then() 메소드 안에 반환값이 존재한다면 then 메소드를 이어서 사용할 수 있는데, 이를 promise chain이라고 한다.

체이닝 했을 경우 순차적으로 이전 단계 비동기 작업이 성공한 다음 그 결과값을 이용해 다음 비동기 작업을 실행한다.

// success1 = 성공 시 실행될 함수1 // success2 = 성공 시 실행될 함수2

// 작성 방법(1)
Promise
  .then(function success1(resolve1){
    return resolve1;
  })
  .then(function success2(resolve2){
    console.log(resolve2);
  });

 

위와 같이 then() 메소드를 통해 체이닝 할 경우 순서는 다음과 같다.

Promise가 성공하면 resolve()된 결과 값이 첫번째 then의 resolve1에 할당된다.

그리고 success1 함수 내용이 실행되고 반환(return)된 resolve1의 값이 두번째 then의 resolve2에 할당된다.

결과적으로 console.log에는 맨 처음 resolve() 되었던 값이 출력될 것이다.

// 작성 방법(2) : 화살표 함수
Promise
  .then((resolve1) => success1(resolve1));
  .then((resolve2) => console.log(resolve2));

 

화살표 함수에서는 return이 생략되기 때문에 위와 같이 작성 가능하다.

이 때, 주의해야할 것은 화살표 함수는 단일 표현식일 경우에만 return이 생략 가능하다는 것이다.

블록(즉, 중괄호 { } )을 포함하고 있다면, 명시적으로 return을 작성해야만 값이 반환된다.

Promise.resolve("작업 성공")
  .then((resolve1) => success1(resolve1));
  .then((resolve2) => console.log(resolve2)); // 작업 성공
  
Promise.resolve("작업 성공")
  .then((resolve1) => { success1(resolve1)} );
  .then((resolve2) => console.log(resolve2)); // undefined

 

then은 새로운 Promise 객체를 생성하는 것이기 때문에 값이 반환되지 않으면 그 객체 안에서만 값을 갖고 있고, 다음 순서의 then에서는 값을 가져오지 못하는 것을 알 수있다.

그렇기 때문에 then 안에서 반드시 값이 반환(return)이 되어야하며, 반환되지 않을 경우 undefined를 결과값으로 가진다.

 

then()의 비동기성

then 메서드의 콜백 함수는 비동기적으로 실행된다.

then의 비동기성을 이해하기 위해선 먼저 자바스크립트가 비동기 처리를 하는 방식을 알아야 한다.

** 자바스크립트는 싱글 스레드로 동작하는데 어떻게 비동기 처리를 할 수 있는 것일까?
자바스크립트는 "호출 스택(Call Stack)"과 "이벤트 루프(Event Loop)" 매커니즘을 사용한다.

1) 호출 스택(Call Stack)은 현재 실행중인 함수 호출을 추적하는 스택 자료 구조이다.
함수가 실행되면 호출 스택에 쌓이고, 함수 실행이 끝나면 호출 스택에서 빠져나온다.

2) 이벤트 루프(Event Loop)는 비동기 작업을 처리하는 매커니즘이다. 
자바스크립트는 싱글 스레드로 동작하지만, 비동기 작업을 처리할 때 백그라운드에서 작업을 수행하고 나중에 이를 호출 스택에 넣어 실행한다.
setTimeout이나 Promise같은 비동기 함수들은 이벤트 큐(Event Queue)에 저장되어 있다가
호출 스택이 비었을 때 이벤트 루프가 호출 스택으로 옮겨 실행한다.

** 이벤트 큐(Event Queue)에는 우선순위가 존재한다.
이벤트 큐에는 마이크로태스크 큐와 매크로태스크 큐가 존재한다.

1) 마이크로태스크 큐(Microtask Queue)
마이크로태스크 큐는 우선순위가 높은 비동기 작업들이 대기하는 곳이다.
이 곳에 등록된 작업들은 호출 스택이 비어있는 즉시 실행된다.
- Promise의 then, catch, finally 콜백 함수
- async / await
- queueMicrotask() API
- MutationObserver 콜백 (DOM 변경 감지)
등이 마이크로태크스 큐에 들어간다.

2) 매크로태스크 큐(Macrotask Queue)
매크로태스크 큐는 마이크로태스크 큐보다 우선순위가 낮은 비동기 작업들이 대기하는 곳이다.
이 곳에 있는 작업들은 호출 스택이 비어있고, 마이크로태스크 큐에 대기중인 작업들이 모두 실행 된 이후에 실행된다.
- setTimeout, setInterval
- I/O 작업 (파일 읽기 / 쓰기 등)
- setImmediate (Node.js)
- UI 렌더링 작업(브라우저의 경우)
- 이벤트 리스너(사용자 입력 등)
등이 매크로태스크 큐에 들어간다.

3) 기타 큐들
자바스크립트에서 마이크로태스크 큐와 매크로태스크 큐 외에도 다른 큐들이 존재할 수 있지만, 일반적으로 위 두개의 큐가 가장 중요하고 자주 다뤄지는 큐들이다. 

4) 예시
실행 순서를 확인하기 위해 다음과 같은 예시를 들어보겠다.
// setp1
console.log("시작");

// setp2
setTimeout(() => {
  console.log("매크로태스크: setTimeout");
}, 0);

Promise.resolve().then(() => {
  // setp3
  console.log("마이크로태스크: Promise then");
}).then(() => {
  // setp4
  console.log("마이크로태스크: 또 다른 Promise then");
});

// setp5
console.log("끝");​

 


위와 같이 코드가 작성되어지면 콘솔에는 아래와 같이 출력될 것이다.
// 시작
// 끝
// 마이크로태스크: Promise then
// 마이크로태스크: 또 다른 Promise then
// 매크로태스크: setTimeout​


즉시 호출되는 setp1과 setp5가 먼저 처리되고 마이크로태스크인 setp3과 setp4가 출력, 이후 매크로태스크인 step5가 출력된다.

 

이제 아래의 예제를 통해 확인해 보겠다.

// setp1) Promise 생성
const resolvedProm = Promise.resolve(33);

// setp2) 호출된 프로미스를 theProm에 담기
let thenProm = resolvedProm.then((value) => {
  console.log(
    "이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 " +
      value +
      "입니다.",
  );
  return value;
});

// setp3) thenProm의 값을 즉시 기록
console.log(thenProm);

// setp4) setTimeout으로 함수 실행을 호출 스택이 빌 때까지 미룰 수 있음
setTimeout(() => {
  console.log(thenProm);
});

 

위와 같이 작성되면 Promise.resolve(33)과 같이 이미 Promise가 해결된 상태라고 할지라도, 그 콜백은 현재 실행 중인 코드 끝난 후, 즉 호출 스택이 비워진 다음에 실행된다.

 

step3에서 console.log(thenProm)은 resolvedProm.then으로 생성된 새로운 Promise를 출력하는 것이기 때문에 thenProm 자체의 pending, resolved 상태를 콘솔에 출력한다.

이때 thenProm으로 새로만들어진 Promise는 무조건 실행되기 전에 pending 상태를 가진다.

이후 then() 메소드를 통해 실행되고 마이크로태스크 큐에 들어가 호출 스택이 비기를 기다리는 과정을 거치기 때문에  실행 속도에 따라 콘솔에서 보이는 값이 달라진다. 

즉, resolvedProm.then()은 즉시 실행되는 것처럼 보이지만, 실제로는 콜백 함수가 마이크로태스크 큐로 들어가기 때문에 호출 스택이 비워진 뒤 실행된다는 것이다.

 

console.log의 자체는 동기적이기 때문에 호출 스택에 바로 쌓여 즉시 출력되어지는데 이 둘의 실행속도에 따라 아래와 같이 결과값이 바뀌는 것이다.

1)  thenProm의 then이 준비완료 되기 전 실행됐을 때
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// "이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 33입니다."
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}

2) thenProm의 then이 준비완료 되기 전 실행됐으나, 브라우저의 콘솔이 디버깅용으로 다르게 표시했을 때
: 브라우저의 개발자 도구(콘솔)는 프로미스가 반환된 값을 디버깅을 위해 미리 보여줄 수 있는 기능이있다. 
이때 thenProm은 아직 pending 상태이지만, 실제로 해당 프로미스가 이미 resolved 상태로 처리되었기 때문에 33이라는 값을 예상해서 보여주는 것이다.
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: 33}
// "이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 33입니다."
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}​

3) thenProm의 then의 준비속도가 console.log보다 빨랐을 때.
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
// "이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 33입니다."
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}​​

 

 

step4에서는 반드시 resolved된 값만을 가져오는 이유는 setTimeout라는 비동기 함수를 사용해 한번 감쌌기 때문이다.

위에서 이벤트 큐의 우선순위에 대해 알아봤었는데, then(마이크로태스크 큐)이 setTimeout(매크로태스크 큐)보다 우선순위가 높기 때문에 then이 먼저 실행된 이후에  setTimeout이 실행되어져서 그렇다.

 

Promise.prototype.catch()

catch()는 프로미스가 실패(거부) 되었을 때 비동기적으로 실행하는 메소드이다.

catch는 then 메소드의 축약형이기도 한데, then에서 첫번째 인수를 null로 가져가는 것과 동일하다.

Promise.then(null, function);
Promise.catch(function);

 

promise chain은 오류가 발생하면 다음에 실행될 chain들 속에서 catch를 찾는다.

일반적인 콜백함수로 작성했을 경우 모든 오류상황에서 실행될 값을 입력해야하나, promise chain에서는 마지막에 한번만 작성하는 것이 가능하다.

 

catch는 체이닝 과정 마지막에만 존재하는 것이 아닌 중간에서도 사용이 가능하다.

아래 예시를 보면 첫번째 then 에서 에러가 발생했기 때문에 do this는 생략되고 발생한 오류로 인해 catch 메소드가 실행된다. catch 메소드가 실행된 이후 then 메소드가 존재한다면 이어서 실행되는 것을 볼 수 있다.

new Promise((resolve, reject) => {
  console.log("Initial");

  resolve();
})
  .then(() => {
    throw new Error("Something failed");

    console.log("Do this");
  })
  .catch(() => {
    console.log("Do that");
  })
  .then(() => {
    console.log("Do this, whatever happened before");
  });

// 콘솔 출력 결과
// Initial
// Do that
// Do this, whatever happened before


이렇게 then과 catch를 통해 체이닝되면 위에서부터 순차적으로 코드가 처리되며 작업의 흐름을 명확하게 관리할 수 있다.

Promise.prototype.finally()

finally()는 Promise가 이행되거나 거부된 후 호출할 함수를 예약하는 메서드이다.

이행된 결과에 관계없이 Promise의 비동기 작업이 끝난 후 항상 실행된다.

이 메서드는 보통 작업 종료 후 추가적인 처리가 필요하거나, 정리 작업이 필요할 경우 사용된다.

Promise.finally(() => 실행하고싶은 내용)

 

finally() 메서드는 Promise의 성공과 실패와는 무관하게 실행되는 메서드이기 때문에 결과값에 영향을 주지 않는다.

finally가 동작하는 모습을 then으로 표현했을 경우 아래와 유사하다.

// finally가 동작하는 모습을 then으로 표현했을 경우

promise.then(
  (value) => Promise.resolve(onFinally()).then(() => value),
  (reason) =>
    Promise.resolve(onFinally()).then(() => {
      throw reason;
    }),
);

 

위 코드에선 onFinally라는 함수가 성공, 실패와 상관없이 항상 실행된다.

Promise.resolve(onFinally())로 감싸는 이유는 onFinally()가 항상 Promise로 반환되도록 보장하기 위해서이다.

 

finally()와 then() 메서드의 차이점

1. finally() 메서드는 한 번만 선언.

then 메서드의 경우 함수를 인라인으로 만들 때, 두 번 선언하거나 변수에 할당해야한다.
Promise.then((resolve) => console.log(resolve), (reject) => console.log(reject));​


하지만 finally의 경우 결과와 무관하게 처리되는 함수를 실행하기 때문에 한번만 사용하는것이 가능하다.

Promise.finally(() => console.log(result));

 

2. finally() 는 인자(Arguments)가 필요없으며, 연결된 Promise의 상태를 변경하지 않는다.

finally는 앞선 then과 catch와 같은 메서드들이 실행된 이후 실행되는 프로미스 체인의 한 축이지만,
단순히 Promise의 성공과 실패와는 무관하게 실행되는 메서드이기 때문에 결과값이나 체이닝을 바꾸지는 않는다.

인자값이 2인 Promise에 then과 finally를 연결하여 값이 바뀌는지 확인해 보겠다.
Promise.resolve(2)
	.then((resolve) => 77)
    	.then((resolve) => console.log(resolve)) // 77

Promise.resolve(2)
	.finally((resolve) => 77)
    	.then((resolve) => console.log(resolve)) // 2​


then 메서드에서 인자값을 77로 변경하면 다음 then에서는 인자값이 77로 나오지만,
finally에서는 77로 변경하였더라도 다음 then 에서는 finally 이전 인자값인 2 그대로 출력된다.


이를 통해, finally는 단순히 마지막 작업을 처리하는 부분이며, 그 자체로 Promise의 결과값이나 체이닝을 바꾸지 않는다는 것을 알 수 있다.

 

3. finally는 내부적으로 then()을 호출한다?

앞선 내용으로 알 수 있듯이 finally는 Promise에 영향을 주는 메서드가 아니다.
새로운 Promise 객체를 리턴하기 때문에 프로미스 체인에 들어가지만, 결과에 영향을 주는 메서드는 아니기 때문에 finally()가 실행된 이후 then()과 같은 후속 메서드가 존재한다면 이어서 실행된다.
결과적으로 finally() 후에 then()이 호출된 것처럼 보이므로 "내부적으로 then()을 호출한다"는 표현이 사용되는 것이다.

*** 만약 콜백함수를 아래와 같이 호출한다면 어떻게 될까?
exPromise.then(success(resolve), failed(reject));​


위와같이 sucess(resolve), failed(reject)로 함수를 호출할 경우, 이는 즉시 호출되는 함수가 되어버린다.

then이 호출되기 전에 sucess와 failed가 먼저 실행되어 버리기 때문에,
undefined 혹은 다른 값을 반환하게 될 수 있다.

만약 위와 같이 인수를 적고 싶다면 다음과 같이 작성해야한다.

exPromise.then(() => success(resolve), () => failed(reject));​

 

*** 만약 두개 이상의 값을 전달받는 경우는?
resolve()에서 두개 이상의 값을 전달하고 싶을 경우 배열 혹은 객체로 전달해야한다.
그렇게 전달된 배열이나 객체를 실행되는 함수 안에서 해체(destructure)하여 사용한다.
// 두 개 이상의 값을 배열로 전달
const exPromise = new Promise((resolve, reject) => {
  resolve([1, 2]);
});

exPromise
  .then((result) => {
  	success(result){
    		// result는 배열 [1, 2]가 될 것
    		console.log(result);  // [1, 2]
    		const [first, second] = result;
    		console.log(first, second);  // 1 2
        }
  })​
// 두 개 이상의 값을 객체로 전달
const exPromise = new Promise((resolve, reject) => {
  resolve({ value1: 1, value2: 2 });
});

exPromise
  .then((result) => {
  	success(result){
    		// result는 객체 { value1: 1, value2: 2 }가 될 것
    		console.log(result);  // { value1: 1, value2: 2 }
    		const { value1, value2 } = result;
    		console.log(value1, value2);  // 1 2
        }
  })


이와같이 배열이나 객체로 받지 않고 한번에 두개 이상의 인자를 받는것은 현재 불가능하다.

만약 두개의 인자를 전달한다면 의도와 달리 첫번째 인자 값만 전달 받는다.

const exPromise = new Promise((resolve, reject) => {
  // 여러 값을 배열로 묶어서 전달
  resolve(1, 2);  // 이렇게 두 인자를 직접 전달하는 방식은 의도한 대로 동작하지 않음
});

exPromise
  .then((result) => {
    // result는 첫 번째 값만 받음
    console.log(result);  // 1 (첫 번째 인자만 출력)
  })​

Promise의 고급 사용법

Promise는 비동기 작업의 동시성을 용이하게 만들기 위해  'Promise.all', 'Promise.race'와 같은 4가지 메소드를 제공한다.

정적 메소드들은 여러 비동기 작업을 동시에 처리하는 기능이다.

이러한 메소드들을 사용하면 비동기 작업을 병렬처리하고 경쟁 상태를 관리할 수 있다.

 

1. Promise.all()

Promise.all()은 여러개의 프로미스들을 비동기적으로 실행한다. 

만약, 여러개의 프로미스들 중 하나라도 reject를 반환하거나 에러가 날 경우, 모든 프로미스를 reject 시킨다.

예시링크1. promise.all로 비동기처리를 해보자

예시링크2. promise.all과 promise.allSettled 비교 정리

 

2. Promise.allSettled

Promise.allSettled()는 주어진 모든 프로미스를 이행하거나 거부한 후, 각 프로미스에 대한 결과를 나타내는 객체 배열을 반환한다.

일반적으로 서로 성공 여부에 관련없는 여러 비동기 작업을 수행해야 하거나, 항상 각 프로미스의 실행결과를 알고 싶을 때 사용한다.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(reject, 100, 'foo'),
);
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) =>
  results.forEach((result) => console.log(result.status)),
);

// Expected output:
// "fulfilled"
// "rejected"

 

3. Promise.any()

Promise.any() 메서드는 순회 가능한 여러 프로미스를 입력으로 받아 단일 프로미스를 반환한다.

이렇게 반환된 프로미스는 입력된 프로미스 중 한개라도 성공하면 이를 이행값으로 하는 프로미스를 반환한다.

const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [promise1, promise2, promise3];

Promise.any(promises).then((value) => console.log(value));

// Expected output: "quick"

 

만약, 입력된 모든 프로미스가 거부되면 최종적으로 이 메서드도 거부되며 AggregateError가 반환된다.

AggregateError는 Error의 하위 클래스로, 다수의 오류가 한 오류로 포장되어야 할 때의 오류를 나타낸다.
// AggregateError 처리하기
Promise.any([Promise.reject(new Error("some error"))]).catch((e) => {
  console.log(e instanceof AggregateError); // true
  console.log(e.message); // "All Promises rejected"
  console.log(e.name); // "AggregateError"
  console.log(e.errors); // [ Error: "some error" ]
});

// AggregateError 발생시키기
try {
  throw new AggregateError([new Error("some error")], "Hello");
} catch (e) {
  console.log(e instanceof AggregateError); // true
  console.log(e.message); // "Hello"
  console.log(e.name); // "AggregateError"
  console.log(e.errors); // [ Error: "some error" ]
}​

 

출처 :  mdn

 

4. Promise.race

Promise.race는 iterable 안에 있는 프로미스 중에 가장 먼저 완료(settle) 된 것을 결과값으로 가져와 Promise로 반환하는 메서드이다.

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// Expected output: "two"

 

만약 전달받은 iterable이 비어 있을 경우, 반환한 프로미스는 영원히 대기 상태가 된다.

iterable에 프로미스가 아닌 값이나 이미 완료된 프로미스가 포함되어 있을 경우, Promise.race는 전달받은 iterable에서 처음으로 등장하는 값을 결과값으로 이행한다.

var foreverPendingPromise = Promise.race([]);
var alreadyFulfilledProm = Promise.resolve(666);

var arr = [foreverPendingPromise, alreadyFulfilledProm, "프로미스 아님"];
var arr2 = [foreverPendingPromise, "프로미스 아님", Promise.resolve(666)];
var p = Promise.race(arr);
var p2 = Promise.race(arr2);

console.log(p);
console.log(p2);
setTimeout(function () {
  console.log("the stack is now empty");
  console.log(p);
  console.log(p2);
});

// 로그 출력 결과 (순서대로):
// Promise { <state>: "pending" }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: 666 }
// Promise { <state>: "fulfilled", <value>: "프로미스 아님" }
// setTimeout과 함께 Promise.race 사용 예제

var p1 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("하나"), 500);
});
var p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("둘"), 100);
});

Promise.race([p1, p2]).then(function (value) {
  console.log(value); // "둘"
  // 둘 다 이행하지만 p2가 더 빠르므로
});

var p3 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("셋"), 100);
});
var p4 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error("넷")), 500);
});

Promise.race([p3, p4]).then(
  function (value) {
    console.log(value); // "셋"
    // p3이 더 빠르므로 이행함
  },
  function (reason) {
    // 실행되지 않음
  },
);

var p5 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve("다섯"), 500);
});
var p6 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error("여섯")), 100);
});

Promise.race([p5, p6]).then(
  function (value) {
    // 실행되지 않음
  },
  function (error) {
    console.log(error.message); // "여섯"
    // p6이 더 빠르므로 거부함
  },
);