Promise 클래스에 존재하는 5가지 정적 메서드에 대해 알아봅시다.
let promise = Promise.all([...promises...]);
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 프라미스 전체가 처리되면 1, 2, 3이 반환
// 각 프라미스는 배열을 구성하는 요소가 됨
여러 개의 프라미스를 동시에 실행시키고 모든 프라미스의 이행을 기다릴 때 사용합니다.
각 프라미스를 배열에 담아 전달하고, 각각의 결과는 result 배열의 요소가 됩니다.
result 가 인수로 온 각 프라미스의 이행 결과를 담은 배열Promise.all 에 전달된 프라미스 중 하나라도 거부되면 Promise.all 은 rejected 상태의 프라미스를 반환합니다.
한 프라미스에서 에러가 발생했다고 다른 프라미스가 중지되는 것은 아닙니다.
다른 프라미스는 그대로 처리됩니다. Promise.all 의 결과가 거부되는 것 뿐입니다.
따라서 다음처럼 모든 프라미스가 이행되어야 하는 상황에 사용합니다.
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // render 메서드는 fetch 결과 전부가 있어야 제대로 동작
Promise.allSettled 는 Promise.all 과 달리 일부 프라미스에서 에러가 나도
여전히 나머지 프라미스의 결과가 필요할 때 사용합니다.
Promise.allSettled 는 결과를 배열로 반환하고, 각 요소는 프라미스 성공여부에 대한 status 와 결과값을 가집니다.
{status:"fulfilled", value:result}{status:"rejected", reason:error}let urls = ['...'];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
Promise.race 는 가장 먼저 처리되는 프라미스의 결과를 반환하며, 다른 프라미스는 무시합니다.
let promise = Promise.race(iterable);
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("에러 발생!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
프라미스 핸들러 then, catch, finally 는 항상 비동기적으로 실행됩니다.
그런데, 프라미스가 즉시 이행되더라도 핸들러 이후의 코드가 먼저 실행되는 것을 확인할 수 있습니다.
let promise = Promise.resolve();
promise.then(() => alert("프라미스 성공!"));
alert("코드 종료"); // 얼럿 창이 가장 먼저 뜸
핸들러가 나중에 트리거 된 이유에 대해 알아봅시다.
비동기 작업 처리를 위해 PromiseJobs 라는 내부 큐가 있는데, V8 엔진은 이를 마이크로태스크 큐 라고 부릅니다.
마이크로태스크 큐는 다음과 같이 동작합니다.
FIFO)여기서 핸들러가 나중에 트리거 된 이유를 알 수 있습니다.
핸들러의 작업은 마이크로태스크 큐에 들어가는데, 실행할 스크립트가 남아있으면(콜 스택에 작업이 남아있으면) 핸들러의 실행은 그 뒤로 미뤄집니다.
따라서 스크립트 실행이 끝난 후에 핸들러가 동작합니다.
핸들러는 비동기적으로, 핸들러끼리는 큐에 들어간 순서대로 동기적으로 실행된다고 볼 수 있습니다.

일반적인 함수는 콜 스택에, 프라미스와 관련한 비동기 작업은 마이크로태스크 큐에,
setTimeout, setInterval 등의 작업은 매크로태스크 큐에 쌓입니다.
이벤트 루프는 콜 스택이 비어있을 때 동작하여 내부 큐의 작업을 실행하도록 합니다.
프라미스 에러 핸들러를 setTimeout 으로 트리거되도록 했을 때 catch 가 아닌 unhandledrejection 이벤트 핸들러가 먼저 실행되는 것은 두 작업이 위치하는 큐가 다르기 때문입니다.
unhandledrejection 이벤트는 마이크로태스크 큐의 작업이 모두 끝나고도 거부된 프라미스가 있을 때 발생합니다. 즉, 매크로태스크 큐의 코드는 살피지 않으므로 에러가 처리되지 않고 이벤트가 발생합니다.
async, await 문법을 쓰면 프라미스를 더 쉽게 사용할 수 있습니다.
asyncasync 키워드를 함수 앞에 붙이면, 그 함수는 항상 프라미스를 반환합니다.
함수가 1을 반환한다고 가정하면, 이를 resolve(1) 을 호출한 프라미스로 바꾸어 반환합니다.
async function f() {
return 1;
}
f().then(alert); // 1
함수가 프라미스를 반환한다면, 그걸 그대로 사용하겠죠?
awaitawait 는 async 함수 안에서만 동작하는 키워드로, 프라미스가 처리될 때까지 기다리도록 합니다.
프라미스가 이행되면, 프라미스의 result 값을 반환합니다.
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("완료!"), 1000)
});
// message 변수에 프라미스 객체 promise 의 result 값을 할당함
let message = await promise; // 프라미스가 이행될 때까지 기다림 (*)
alert(message); // "완료!"
}
f();
await 줄에서 함수 실행이 잠시 중단되었다가 프라미스가 처리되면 다시 실행을 재개합니다.
즉, await 이후의 코드는 프라미스 처리가 완료된 후에 실행됩니다.
await 가 완료된 프라미스의 결과값을 반환하므로 바로 result 를 변수에 할당할 수 있습니다.
await 는 호환성을 위해 then 메서드를 사용할 수 있는 thenable 객체를 받습니다.
await promise 에서 발생한 에러는 throw 로 에러를 던진 것과 동일한 상황입니다.
async function f() {
await Promise.reject(new Error("에러 발생!"));
}
// 같음
async function f() {
throw new Error("에러 발생!");
}
따라서, try..catch 를 사용하여 에러를 잡을 수 있습니다.