15. Promise
Promise 는 "미래의 어느 시점에 완료될 작업의 결과를 담는 상자"입니다. 세 가지 상태가 있습니다.
이 강의에서 배우는 것
- 1Promise 의 세 가지 상태(pending, fulfilled, rejected)를 안다.
- 2`new Promise` 로 직접 Promise 를 생성할 수 있다.
- 3`then`, `catch`, `finally` 로 결과를 처리한다.
- 4체이닝으로 콜백 지옥을 평탄화할 수 있다.
- 5`Promise.all`, `Promise.race` 의 차이를 안다.
소개
Promise 는 "미래의 어느 시점에 완료될 작업의 결과를 담는 상자"입니다. 세 가지 상태가 있습니다.
핵심 개념
1. Promise 란?
Promise 는 "미래의 어느 시점에 완료될 작업의 결과를 담는 상자"입니다. 세 가지 상태가 있습니다.
- pending: 아직 끝나지 않음
- fulfilled: 성공, 값이 있음
- rejected: 실패, 이유(에러)가 있음
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve(42), 100);
});`resolve` 를 호출하면 fulfilled, `reject` 를 호출하면 rejected 가 됩니다. 한 번 정해진 상태는 바뀌지 않습니다.
2. then / catch / finally
p.then((value) => console.log(value))
.catch((err) => console.error(err))
.finally(() => console.log("끝"));`then` 의 콜백에서 반환한 값은 다음 `then` 의 인자로 들어갑니다. 에러가 발생하면 가까운 `catch` 가 잡습니다.
3. 체이닝
콜백 지옥 예제를 Promise 로 다시 쓰면 평탄해집니다.
step("1").then(() => step("2")).then(() => step("3"));`then` 안에서 Promise 를 반환하면 그것이 완료될 때까지 다음 `then` 이 기다립니다.
4. Promise.all / Promise.race
- `Promise.all([p1, p2, ...])`: 모두 성공해야 성공, 하나라도 실패하면 실패. 결과는 배열.
- `Promise.race([p1, p2, ...])`: 가장 먼저 결정(성공/실패)된 결과를 채택.
- `Promise.allSettled`: 모두 끝날 때까지 기다리고, 각각의 상태 객체 배열을 돌려줌.
핵심 예제
| 파일 | 다루는 내용 |
|---|---|
| 01_new_promise.js | new Promise 로 직접 만들기 |
| 02_then_catch.js | then/catch/finally 사용 |
| 03_chaining.js | 체이닝으로 순차 실행 |
| 04_all_race.js | Promise.all / Promise.race |
src/01_new_promise.js
/**
* new Promise 로 비동기 작업을 표현합니다.
*/
const promise = new Promise((resolve, reject) => {
const ok = Math.random() > 0.3;
setTimeout(() => {
if (ok) resolve("성공!");
else reject(new Error("실패..."));
}, 200);
});
promise
.then((value) => console.log("결과:", value))
.catch((err) => console.error("에러:", err.message));
src/02_then_catch.js
/**
* then / catch / finally 의 흐름을 보여줍니다.
*/
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) reject(new Error("잘못된 ID"));
else resolve({ id, name: "지민" });
}, 100);
});
}
fetchUser(1)
.then((user) => console.log("user:", user))
.catch((err) => console.error(err.message))
.finally(() => console.log("요청 종료"));
fetchUser(-1)
.then((user) => console.log(user))
.catch((err) => console.error("에러:", err.message))
.finally(() => console.log("두 번째 요청 종료"));
src/03_chaining.js
/**
* Promise 체이닝으로 비동기 단계를 순차 실행합니다.
*/
function step(name, ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${name} 완료`);
resolve(name);
}, ms);
});
}
step("A", 100)
.then((prev) => step(`B(after ${prev})`, 100))
.then((prev) => step(`C(after ${prev})`, 100))
.then((last) => console.log("최종:", last));
src/04_all_race.js
/**
* Promise.all 과 Promise.race 비교.
*/
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
Promise.all([delay(100, "A"), delay(200, "B"), delay(150, "C")]).then((values) => {
console.log("all:", values);
});
Promise.race([delay(100, "X"), delay(50, "Y"), delay(80, "Z")]).then((value) => {
console.log("race winner:", value);
});
자주 하는 실수
- `then` 에서 값을 반환하지 않아 다음 `then` 에서 undefined 를 받음.
- `catch` 없이 사용해 unhandled rejection 발생.
- `Promise.all` 에 하나라도 실패하면 전체 실패라는 점을 잊음.
- `new Promise` 안에서 throw 한 에러는 자동으로 reject 가 됨을 모름.
- Promise 가 즉시 실행됨을 모르고 "lazy" 라고 생각.
FAQ
Q1. resolve/reject 는 즉시 호출해도 되나요?
됩니다. 그래도 then 콜백은 비동기로 실행됩니다.
Q2. catch 와 then 의 두 번째 인자 차이?
then(onFul, onRej) 의 onRej 는 같은 then 의 onFul 에서 던진 에러는 잡지 못합니다. catch 가 더 안전합니다.
Q3. Promise 안에 Promise 를 반환하면?
자동으로 풀려서 then 다음 단계로 값이 전달됩니다.
과제
- setTimeout 기반 `delay(ms)` Promise 함수.
- Promise.all 로 여러 작업을 병렬 실행하는 코드.
homework/README.md
## 1. delay 함수 `delay(ms)` 가 ms 밀리초 후 resolve 되는 Promise 를 돌려주도록 작성하세요.
## 2. 병렬 합산 세 개의 비동기 숫자 값을 Promise.all 로 받아 합계를 출력하세요.
답안은 `answer/` 디렉터리를 참고하세요.
homework/answer/homework_01.js
/**
* 주어진 시간만큼 기다린 뒤 resolve 되는 Promise.
* @param {number} ms 대기 밀리초
* @returns {Promise<void>}
*/
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
console.log("시작");
delay(500).then(() => console.log("0.5초 경과"));
homework/answer/homework_02.js
/**
* 세 개의 비동기 숫자를 병렬로 받아 합을 출력합니다.
*/
function asyncNumber(n, ms) {
return new Promise((resolve) => setTimeout(() => resolve(n), ms));
}
Promise.all([asyncNumber(10, 100), asyncNumber(20, 150), asyncNumber(30, 80)])
.then((nums) => {
const sum = nums.reduce((a, b) => a + b, 0);
console.log("합계:", sum);
});
다음 단원
[16_async_await](../16_async_await/) — Promise 를 더 쉽게 쓰는 문법