Skip to content

Commit 0298521

Browse files
committed
feat: add chapter on promises with detailed explanations and examples
1 parent d0c2797 commit 0298521

File tree

1 file changed

+266
-0
lines changed
  • src/content/45장 프로미스

1 file changed

+266
-0
lines changed
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# 45장 프로미스
2+
3+
자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 전통적인 콜백 패턴은 **콜백 헬(Callback Hell)**로 인해 가독성이 나쁘고, 비동기 처리 중 발생한 에러의 처리가 곤란하며, 여러 개의 비동기 처리를 한 번에 처리하는 데도 한계가 있다.
4+
5+
ES6에서는 비동기 처리를 위한 또 다른 패턴으로 **프로미스(Promise)**를 도입했다. 프로미스는 전통적인 콜백 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다.
6+
7+
## 45.1 비동기 처리를 위한 콜백 패턴의 단점
8+
9+
### 45.1.1 콜백 헬
10+
11+
비동기 함수는 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다. 따라서 비동기 함수의 처리 결과(성공/실패)에 대한 후속 처리는 비동기 함수 내부에서 수행해야 한다.
12+
13+
이때 후속 처리를 위해 콜백 함수를 전달하는데, 콜백 함수 내에서 다시 비동기 호출을 하면서 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상을 **콜백 헬**이라 한다.
14+
15+
```javascript
16+
// 콜백 헬 예시
17+
get('/step1', (a) => {
18+
get(`/step2/${a}`, (b) => {
19+
get(`/step3/${b}`, (c) => {
20+
console.log(c);
21+
});
22+
});
23+
});
24+
```
25+
26+
### 45.1.2 에러 처리의 한계
27+
28+
가장 심각한 문제는 **에러 처리가 곤란하다**는 것이다.
29+
30+
```javascript
31+
try {
32+
setTimeout(() => {
33+
throw new Error('Error!');
34+
}, 1000);
35+
} catch (e) {
36+
// 에러를 캐치하지 못한다.
37+
console.error('캐치한 에러', e);
38+
}
39+
```
40+
41+
`setTimeout`의 콜백 함수가 실행될 때 `setTimeout` 함수는 이미 콜 스택에서 제거된 상태다. 에러는 호출자(caller) 방향으로 전파되는데, 콜백 함수의 호출자가 `setTimeout`이 아니기 때문에 `catch` 블록에서 에러를 잡을 수 없다.
42+
43+
## 45.2 프로미스의 생성
44+
45+
`Promise` 생성자 함수를 `new` 연산자와 함께 호출하면 프로미스 객체를 생성한다.
46+
47+
```javascript
48+
// 프로미스 생성
49+
const promise = new Promise((resolve, reject) => {
50+
// 비동기 작업 수행
51+
if (/* 비동기 처리 성공 */) {
52+
resolve('result');
53+
} else { /* 비동기 처리 실패 */
54+
reject('failure reason');
55+
}
56+
});
57+
```
58+
59+
### 프로미스의 상태 정보
60+
61+
| 상태 정보 | 의미 | 상태 변경 조건 |
62+
| :------------ | :------------------------------------ | :------------------------------- |
63+
| **pending** | 비동기 처리가 아직 수행되지 않은 상태 | 프로미스가 생성된 직후 기본 상태 |
64+
| **fulfilled** | 비동기 처리가 수행된 상태 (성공) | `resolve` 함수 호출 |
65+
| **rejected** | 비동기 처리가 수행된 상태 (실패) | `reject` 함수 호출 |
66+
67+
`fulfilled` 또는 `rejected` 상태를 **settled** 상태라고 한다. 일단 `settled` 상태가 되면 더 이상 다른 상태로 변화할 수 없다.
68+
69+
## 45.3 프로미스의 후속 처리 메서드
70+
71+
프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드(`then`, `catch`, `finally`)에 인수로 전달한 콜백 함수가 선택적으로 호출된다. **모든 후속 처리 메서드는 프로미스를 반환하며, 비동기로 동작한다.**
72+
73+
### 45.3.1 Promise.prototype.then
74+
75+
두 개의 콜백 함수를 인수로 받는다.
76+
77+
- 첫 번째: `fulfilled` 상태(성공) 시 호출
78+
- 두 번째: `rejected` 상태(실패) 시 호출
79+
80+
```javascript
81+
new Promise((resolve) => resolve('fulfilled')).then(
82+
(v) => console.log(v),
83+
(e) => console.error(e)
84+
);
85+
```
86+
87+
### 45.3.2 Promise.prototype.catch
88+
89+
`rejected` 상태(실패) 시 호출될 콜백 함수를 인수로 받는다. `then(undefined, onRejected)`와 동일하게 동작한다.
90+
91+
### 45.3.3 Promise.prototype.finally
92+
93+
성공/실패와 상관없이 무조건 한 번 호출된다. 공통적인 뒷정리 작업에 유용하다.
94+
95+
## 45.4 프로미스의 에러 처리
96+
97+
에러 처리는 `then` 메서드의 두 번째 인수보다 **`catch` 메서드를 사용하는 것을 권장**한다. `catch` 메서드를 사용하면 `then` 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있고 가독성도 더 좋다.
98+
99+
```javascript
100+
promiseGet(url)
101+
.then((res) => console.log(res))
102+
.catch((err) => console.error(err)); // 권장
103+
```
104+
105+
## 45.5 프로미스 체이닝
106+
107+
`then`, `catch`, `finally` 후속 처리 메서드는 언제나 프로미스를 반환하므로 연속적으로 호출할 수 있다. 이를 **프로미스 체이닝(Promise Chaining)**이라 한다.
108+
109+
```javascript
110+
promiseGet(`${url}/posts/1`)
111+
.then(({ userId }) => promiseGet(`${url}/users/${userId}`))
112+
.then((userInfo) => console.log(userInfo))
113+
.catch((err) => console.error(err));
114+
```
115+
116+
## 45.6 프로미스의 정적 메서드
117+
118+
### 45.6.1 Promise.resolve / Promise.reject
119+
120+
이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용한다.
121+
122+
- `Promise.resolve`는 인수로 전달받은 값을 `resolve`하는 프로미스를 생성한다.
123+
- `Promise.reject`는 인수로 전달받은 값을 `reject`하는 프로미스를 생성한다.
124+
125+
### 45.6.2 Promise.all
126+
127+
여러 개의 비동기 처리를 모두 병렬(parallel) 처리할 때 사용한다. 전달받은 모든 프로미스가 `fulfilled` 상태가 되면 모든 결과를 배열에 담아 반환한다. 하나라도 `rejected` 되면 즉시 에러를 반환한다.
128+
129+
### 45.6.3 Promise.race
130+
131+
가장 먼저 `fulfilled` 또는 `rejected` 상태가 된 프로미스의 결과를 반환한다.
132+
133+
### 45.6.4 Promise.allSettled
134+
135+
전달받은 프로미스가 모두 `settled` 상태(성공 또는 실패)가 되면 결과를 반환한다. 실패하더라도 중단되지 않고 모든 결과를 확인할 수 있다.
136+
137+
## 45.7 마이크로태스크 큐
138+
139+
프로미스의 후속 처리 메서드(`then`, `catch`, `finally`)의 콜백 함수는 **태스크 큐**가 아니라 **마이크로태스크 큐**에 저장된다.
140+
**마이크로태스크 큐는 태스크 큐보다 우선순위가 높다.**
141+
142+
```javascript
143+
setTimeout(() => console.log(1), 0); // 태스크 큐
144+
145+
Promise.resolve()
146+
.then(() => console.log(2)) // 마이크로태스크 큐
147+
.then(() => console.log(3)); // 마이크로태스크 큐
148+
149+
// 출력 순서: 2 -> 3 -> 1
150+
```
151+
152+
## 45.8 fetch
153+
154+
`fetch` 함수는 `XMLHttpRequest` 객체보다 사용법이 간단하고 프로미스를 지원하는 HTTP 요청 전송 기능의 Web API다.
155+
156+
```javascript
157+
fetch('https://jsonplaceholder.typicode.com/todos/1')
158+
.then((response) => response.json())
159+
.then((json) => console.log(json));
160+
```
161+
162+
**주의할 점**: `fetch` 함수가 반환하는 프로미스는 **HTTP 에러(404, 500 등)가 발생해도 `reject`하지 않고 `resolve`한다.** (단, `ok` 상태를 `false`로 설정). 오직 네트워크 장애나 요청이 완료되지 못한 경우에만 `reject`한다.
163+
따라서 `response.ok`를 확인하여 에러 처리를 해야 한다.
164+
165+
```javascript
166+
fetch(wrongUrl)
167+
.then((response) => {
168+
if (!response.ok) throw new Error(response.statusText);
169+
return response.json();
170+
})
171+
.catch((err) => console.error(err));
172+
```
173+
174+
## 과제: 퀴즈
175+
176+
### 퀴즈 1
177+
178+
프로미스의 3가지 상태(state)는 무엇인가?
179+
180+
<details>
181+
<summary>정답 및 해설</summary>
182+
183+
**정답:** `pending`(대기), `fulfilled`(이행), `rejected`(거부)
184+
185+
**해설:**
186+
187+
- **pending**: 비동기 처리가 아직 수행되지 않은 기본 상태.
188+
- **fulfilled**: 비동기 처리가 성공적으로 수행된 상태 (`resolve` 호출 시).
189+
- **rejected**: 비동기 처리가 실패한 상태 (`reject` 호출 시).
190+
`fulfilled``rejected`를 합쳐 `settled` 상태라고 부르며, 한 번 `settled` 되면 상태는 변하지 않습니다.
191+
192+
[👉 관련 내용으로 이동: 45.2 프로미스의 생성](#452-프로미스의-생성)
193+
194+
</details>
195+
196+
### 퀴즈 2
197+
198+
프로미스 체이닝에서 에러 처리를 위해 `then`의 두 번째 인수를 사용하는 것보다 `catch`를 권장하는 이유는?
199+
200+
<details>
201+
<summary>정답 및 해설</summary>
202+
203+
**정답:** `then` 내부의 에러까지 잡을 수 있고 가독성이 좋기 때문.
204+
205+
**해설:**
206+
`then`의 두 번째 콜백 함수는 같은 `then`의 첫 번째 콜백 함수에서 발생한 에러를 잡을 수 없습니다. 반면 `catch`는 그 앞의 모든 `then` 체인과 비동기 처리에서 발생한 에러를 모두 잡을 수 있으며 코드 가독성도 더 뛰어납니다.
207+
208+
[👉 관련 내용으로 이동: 45.4 프로미스 에러 처리](#454-프로미스-에러-처리)
209+
210+
</details>
211+
212+
### 퀴즈 3
213+
214+
`Promise.all``Promise.race`의 차이점은?
215+
216+
<details>
217+
<summary>정답 및 해설</summary>
218+
219+
**정답:** `all`은 모두 성공해야 완료, `race`는 가장 빠른 하나만 완료되면 종료.
220+
221+
**해설:**
222+
223+
- **Promise.all**: 전달받은 모든 프로미스가 `fulfilled` 될 때까지 기다렸다가 결과 배열을 반환합니다. 하나라도 실패하면 즉시 실패 처리됩니다.
224+
- **Promise.race**: 전달받은 프로미스 중 가장 먼저 처리된(성공이든 실패든) 프로미스의 결과를 그대로 반환합니다.
225+
226+
[👉 관련 내용으로 이동: 45.6 프로미스의 정적 메서드](#456-프로미스의-정적-메서드)
227+
228+
</details>
229+
230+
### 퀴즈 4
231+
232+
`fetch` 함수 사용 시 HTTP 404 에러가 발생하면 `catch` 블록이 실행되는가?
233+
234+
<details>
235+
<summary>정답 및 해설</summary>
236+
237+
**정답:** 실행되지 않는다. (오답 주의!)
238+
239+
**해설:**
240+
`fetch`는 네트워크 장애 등을 제외한 HTTP 에러(4xx, 5xx)에 대해서는 프로미스를 `reject` 하지 않습니다. 대신 `resolve` 상태의 응답 객체를 반환하며 `ok` 프로퍼티가 `false`가 됩니다. 따라서 `response.ok`를 체크하여 수동으로 에러를 던져야 합니다.
241+
242+
[👉 관련 내용으로 이동: 45.8 fetch](#458-fetch)
243+
244+
</details>
245+
246+
### 퀴즈 5
247+
248+
마이크로태스크 큐에 들어가는 대표적인 작업은 무엇인가?
249+
250+
<details>
251+
<summary>정답 및 해설</summary>
252+
253+
**정답:** 프로미스의 후속 처리 메서드(`then`, `catch`, `finally`)의 콜백 함수.
254+
255+
**해설:**
256+
`setTimeout`, `setInterval` 등의 콜백은 일반 **태스크 큐**에 들어가는 반면, 프로미스의 핸들러는 우선순위가 높은 **마이크로태스크 큐**에 들어갑니다. 따라서 이벤트 루프는 콜 스택이 비면 태스크 큐보다 마이크로태스크 큐를 먼저 비웁니다.
257+
258+
[👉 관련 내용으로 이동: 45.7 마이크로태스크 큐](#457-마이크로태스크-큐)
259+
260+
</details>
261+
262+
## 추천 자료
263+
264+
- [MDN - Promise](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise)
265+
- [MDN - Using Promises](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Using_promises)
266+
- [Modern JavaScript Deep Dive 45장](http://www.yes24.com/Product/Goods/92742567)

0 commit comments

Comments
 (0)