Skip to content

Commit 70a0365

Browse files
[콜백] 보완
1 parent 5808683 commit 70a0365

File tree

1 file changed

+41
-41
lines changed

1 file changed

+41
-41
lines changed

1-js/11-async/01-callbacks/article.md

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22

3-
# 콜백 입문
3+
# 콜백
44

55
```warn header="브라우저 메서드를 사용합니다."
66
콜백, 프라미스 등을 어떻게 사용하는지 보여드리기 위해 본 챕터에선 브라우저 전용 메서드를 사용할 예정입니다. 스크립트를 불러오고 완성된 문서에 간단한 조작을 하는 예시에서 특히 브라우저 메서드가 사용될 예정입니다.
@@ -10,36 +10,36 @@
1010
독자를 위해 최대한 브라우저 메서드를 덜 쓰도록 하겠습니다.
1111
```
1212

13-
자바스크립트 내 동작 상당수는 *비동기(asynchronous)* 로 처리됩니다. 이는 지금 당장 동작을 시작해도 동작이 끝나는 시점은 나중이라는 것을 의미합니다.
13+
자바스크립트 호스트 환경이 제공하는 여러 함수를 사용하면 *비동기(asynchronous)* 동작을 스케줄링 할 수 있습니다. 원하는 때에 동작이 시작하도록 할 수 있죠.
1414

15-
`setTimeout`을 사용해 비동기 동작을 스케줄링하는 것이 대표적인 예시입니다.
15+
`setTimeout`은 스케줄링에 사용되는 가장 대표적인 함수입니다.
1616

17-
비동기 동작에 대한 예시는 다양합니다. 스크립트나 모듈을 로딩(뒤에서 다루게 될 내용)하는 것 또한 비동기 동작입니다.
17+
실무에서 맞닥뜨리는 비동기 동작은 아주 다양합니다. 스크립트나 모듈을 로딩하는 것 또한 비동기 동작입니다(이 예시는 뒤에서 구체적으로 다룰 예정입니다).
1818

19-
`src`에 있는 스크립트를 읽어오는 함수 `loadScript(src)`를 살펴봅시다.
19+
`src`에 있는 스크립트를 읽어오는 함수 `loadScript(src)`예시로 비동기 동작 처리가 어떻게 일어나는지 살펴봅시다.
2020

2121
```js
2222
function loadScript(src) {
23-
// creates a <script> tag and append it to the page
24-
// this causes the script with given src to start loading and run when complete
23+
// <script> 태그를 만들고 페이지에 태그를 추가합니다.
24+
// 태그가 페이지에 추가되면 src에 있는 스크립트를 로딩하고 실행합니다.
2525
let script = document.createElement('script');
2626
script.src = src;
2727
document.head.append(script);
2828
}
2929
```
3030

31-
위 함수는 `<script src="…">`를 동적으로 만들고 이를 문서에 추가합니다. 브라우저는 자동으로 태그에 있는 스크립트를 읽고 읽기가 끝나면 실행합니다.
31+
함수 `loadScript(src)` `<script src="…">`를 동적으로 만들고 이를 문서에 추가합니다. 브라우저는 자동으로 태그에 있는 스크립트를 불러오고, 로딩이 완료되면 스크립트를 실행합니다.
3232

33-
함수 사용법은 다음과 같습니다.
33+
`loadScript(src)` 사용법은 다음과 같습니다.
3434

3535
```js
3636
// 해당 경로에 위치한 스크립트를 불러오고 실행함
3737
loadScript('/my/script.js');
3838
```
3939

40-
스크립트 읽기가 지금 당장 시작되더라도 실행은 함수가 종료되고 난 후에야 실행되므로 스크립트는 '비동기적으로' 실행되었다고 할 수 있습니다.
40+
그런데 이 때 스크립트는 '비동기적으로' 실행됩니다. 로딩은 지금 당장 시작되더라도 실행은 함수가 끝난 후에야 되기 때문입니다.
4141

42-
`loadScript(…)` 아래에 코드가 있더라도 이 코드들은 스크립트 로딩이 종료되는 걸 기다리지 않습니다.
42+
따라서 `loadScript(…)` 아래에 있는 코드들은 스크립트 로딩이 종료되는 걸 기다리지 않습니다.
4343

4444
```js
4545
loadScript('/my/script.js');
@@ -50,7 +50,7 @@ loadScript('/my/script.js');
5050

5151
스크립트 로딩이 끝나자마자 이 스크립트를 사용해 무언가를 해야만 한다고 가정해 봅시다. 스크립트 안에 다양한 함수가 정의되어 있고, 우리는 이 함수를 실행하길 원하는 상황입니다.
5252

53-
그런데 `loadScript(...)`를 호출하자마자 내부 함수를 호출하면, 원하는 대로 작동하지 않습니다.
53+
그런데 `loadScript(...)`를 호출하자마자 내부 함수를 호출하면 원하는 대로 작동하지 않습니다.
5454

5555
```js
5656
loadScript('/my/script.js'); // script.js엔 "function newFunction() {…}"이 있습니다.
@@ -60,9 +60,9 @@ newFunction(); // 함수가 존재하지 않는다는 에러가 발생합니다!
6060
*/!*
6161
```
6262

63-
브라우저는 당연히 스크립트를 읽어올 수 있는 시간을 충분히 확보하지 못합니다. 현재로서는 함수 `loadScript`에서 스크립트 로딩이 완료되었는지 알 수 있는 방법이 없습니다. 언젠간 스크립트가 로드되고 실행도 되겠지만, 그게 다입니다. 그런데, 스크립트 안의 함수나 변수를 사용하려면 스크립트 로딩이 끝났는지 여부를 알아야 합니다.
63+
에러는 브라우저가 스크립트를 읽어올 수 있는 시간을 충분히 확보하지 못했기 때문에 발생합니다. 그런데 현재로서는 함수 `loadScript`에서 스크립트 로딩이 완료되었는지 알 방법이 없네요. 언젠간 스크립트가 로드되고 실행도 되겠지만, 그게 다입니다. 원하는 대로 스크립트 안의 함수나 변수를 사용하려면 스크립트 로딩이 끝났는지 여부를 알 수 있어야 합니다.
6464

65-
`loadScript`의 두 번째 인수로 스크립트 로딩이 끝난 후 실행될 함수인 `콜백(callback)` 함수를 추가해 봅시다(콜백은 나중에 호출할 함수를 의미합니다. - 옮긴이).
65+
`loadScript`의 두 번째 인수로 스크립트 로딩이 끝난 후 실행될 함수인 `콜백(callback)` 함수를 추가해 봅시다(콜백 함수는 나중에 호출할 함수를 의미합니다. - 옮긴이).
6666

6767
```js
6868
function loadScript(src, *!*callback*/!*) {
@@ -77,7 +77,7 @@ function loadScript(src, *!*callback*/!*) {
7777
}
7878
```
7979

80-
이제 `loadScript`불러온 스크립트에 있는 새로운 함수를 호출하려면, 호출 코드를 콜백 함수 안에 작성해야만 합니다.
80+
새롭게 불러온 스크립트에 있는 함수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트 안의 함수를 사용할 수 있습니다.
8181

8282
```js
8383
loadScript('/my/script.js', function() {
@@ -87,7 +87,7 @@ loadScript('/my/script.js', function() {
8787
});
8888
```
8989

90-
이렇게 두 번째 인수로 전달된 함수(대개 익명 함수)는 원하는 동작(위 예제에선 외부 스크립트를 불러오는 것 -옮긴이)이 완료되었을 때 실행됩니다.
90+
이렇게 두 번째 인수로 전달된 함수(대개 익명 함수)는 원하는 동작(위 예제에선 외부 스크립트를 불러오는 것 - 옮긴이)이 완료되었을 때 실행됩니다.
9191

9292
아래는 실제 존재하는 스크립트를 이용해 만든 예시입니다. 직접 실행해 봅시다.
9393

@@ -107,15 +107,15 @@ loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', s
107107
*/!*
108108
```
109109

110-
이런 방식을 '콜백 기반(callback-based)' 비동기 프로그래밍이라고 합니다. 무언가를 비동기적으로 처리하는 함수는 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어갈 `콜백`을 인수로 반드시 제공해야 합니다.
110+
이런 방식을 '콜백 기반(callback-based)' 비동기 프로그래밍이라고 합니다. 무언가를 비동기적으로 수행하는 함수는 함수 내 동작이 모두 처리된 후 실행되어야 하는 함수가 들어갈 `콜백`을 인수로 반드시 제공해야 합니다.
111111

112112
위 예시에선 `loadScript`의 인수로 콜백을 제공해 주었는데, 이렇게 콜백을 사용한 방식은 비동기 프로그래밍의 일반적인 접근법입니다.
113113

114114
## 콜백 속 콜백
115115

116-
두 스크립트를 어떻게 하면 순차적으로 불러올 수 있을까요? 스크립트 하나 로딩이 끝나면, 다음 스크립트를 로딩하는 것 같이 말이죠.
116+
스크립트가 두 개 있는 경우, 어떻게 하면 두 스크립트를 순차적으로 불러올 수 있을까요? 두 번째 스크립트 로딩은 첫 번째 스크립트의 로딩이 끝난 이후가 되길 원한다면 말이죠.
117117

118-
가장 자연스러운 해결 방법은 아래와 같이 콜백 함수 안에서 두 번째 `loadScript` 함수를 호출하는 것입니다.
118+
가장 자연스러운 해결 방법은 아래와 같이 콜백 함수 안에서 두 번째 `loadScript` 호출하는 것입니다.
119119

120120
```js
121121
loadScript('/my/script.js', function(script) {
@@ -131,9 +131,9 @@ loadScript('/my/script.js', function(script) {
131131
});
132132
```
133133

134-
바깥에 위치한 `loadScript`가 완료된 후, 콜백은 안쪽 `loadScript`를 실행합니다.
134+
이렇게 중첩 콜백을 만들면 바깥에 위치한 `loadScript`가 완료된 후, 안쪽 `loadScript`가 실행됩니다.
135135

136-
만약 스크립트를 하나 더 불러오고 싶다면 어떻게 해야 할까요?
136+
그런데 여기에 더하여 스크립트를 하나 더 불러오고 싶다면 어떻게 해야 할까요?
137137

138138
```js
139139
loadScript('/my/script.js', function(script) {
@@ -151,13 +151,13 @@ loadScript('/my/script.js', function(script) {
151151
});
152152
```
153153

154-
위와 같이 작성하면 됩니다. 모든 새로운 동작이 콜백 안에 위치하게 되었습니다. 수행하려는 동작이 단 몇 개뿐이라면 이렇게 작성해도 괜찮지만, 동작이 많은 경우는 이런 식으로 작성하는 게 좋지 않습니다. 다른 방식으로 코드를 작성하는 방법에 대해선 곧 알아보도록 하겠습니다.
154+
위와 같이 모든 새로운 동작이 콜백 안에 위치하게 작성하면 됩니다. 그런데 이렇게 콜백 안에 콜백을 넣는 것은 수행하려는 동작이 단 몇 개뿐이라면 괜찮지만, 동작이 많은 경우엔 좋지 않습니다. 다른 방식으로 코드를 작성하는 방법은 곧 알아보도록 하겠습니다.
155155

156156
## 에러 핸들링
157157

158-
위 예시는 스크립트 로딩이 실패하는 경우 등의 에러를 고려하지 않고 작성되었습니다. 콜백 함수는 에러를 핸들링할 수 있어야 합니다.
158+
지금까지 살펴본 예시들은 스크립트 로딩이 실패하는 경우 등의 에러를 고려하지 않고 작성되었습니다. 그런데 스크립트 로딩이 실패할 가능성은 언제나 있습니다. 물론 콜백 함수는 이런 에러를 핸들링할 수 있어야 하죠.
159159

160-
아래 `loadScript` 로딩 에러를 추적할 수 있습니다.
160+
`loadScript`에서 로딩 에러를 추적할 수 있게 기능을 개선해봅시다.
161161

162162
```js
163163
function loadScript(src, callback) {
@@ -166,16 +166,16 @@ function loadScript(src, callback) {
166166

167167
*!*
168168
script.onload = () => callback(null, script);
169-
script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생함`));
169+
script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));
170170
*/!*
171171

172172
document.head.append(script);
173173
}
174174
```
175175

176-
`loadScript`는 스크립트 로딩에 성공하면 `callback(null, script)`을, 실패하면 `callback(error)`을 호출합니다.
176+
이제 `loadScript`는 스크립트 로딩에 성공하면 `callback(null, script)`을, 실패하면 `callback(error)`을 호출합니다.
177177

178-
사용법:
178+
개선된 `loadScript`의 사용법은 다음과 같습니다.
179179
```js
180180
loadScript('/my/script.js', function(error, script) {
181181
if (error) {
@@ -186,17 +186,17 @@ loadScript('/my/script.js', function(error, script) {
186186
});
187187
```
188188

189-
`loadScript` 같이 콜백을 사용해 에러를 처리하는 방식은 흔히 사용됩니다. 이런 오류 처리 방식은 '오류 우선 콜백(error-first callback)' 스타일이라고 불립니다.
189+
이렇게 에러를 처리하는 방식은 흔히 사용되는 패턴입니다. 이런 패턴은 '오류 우선 콜백(error-first callback)'이라고 불립니다.
190190

191191
오류 우선 콜백은 다음 관례를 따릅니다.
192192
1. `callback`의 첫 번째 인수는 에러를 위해 남겨둡니다. 에러가 발생하면 이 인수를 이용해 `callback(err)`이 호출됩니다.
193193
2. 두 번째 인수(필요하면 인수를 더 추가할 수 있음)는 에러가 발생하지 않았을 때를 위해 남겨둡니다. 원하는 동작이 성공한 경우엔 `callback(null, result1, result2...)`이 호출됩니다.
194194

195-
이처럼 '오류 우선 콜백' 스타일을 사용하면, 단일 `콜백` 함수에서 에러 케이스와 성공 케이스 모두를 처리할 수 있습니다.
195+
오류 우선 콜백 스타일을 사용하면, 단일 `콜백` 함수에서 에러 케이스와 성공 케이스 모두를 처리할 수 있습니다.
196196

197197
## 멸망의 피라미드
198198

199-
콜백 기반 비동기 처리는 꽤 쓸만해 보이고, 실제로도 그렇습니다. 한 개 혹은 두 개의 중첩 호출이 있는 경우는 보기에도 나쁘지 않습니다.
199+
콜백 기반 비동기 처리는 언뜻 봤을 때 꽤 쓸만해 보이고, 실제로도 그렇습니다. 한 개 혹은 두 개의 중첩 호출이 있는 경우는 보기에도 나쁘지 않습니다.
200200

201201
하지만 꼬리에 꼬리를 무는 비동기 동작이 많아지면 아래와 같은 코드 작성이 불가피해집니다.
202202

@@ -229,13 +229,13 @@ loadScript('1.js', function(error, script) {
229229
```
230230

231231
위 코드는 다음과 같이 동작합니다.
232-
1. `1.js`를 로드합니다, 그 후 에러가 없으면,
233-
2. `2.js`를 로드합니다, 그 후 에러가 없으면,
234-
3. `3.js`를 로드합니다, 그 후 에러가 없으면 `(*)`로 표시한 줄에서 또 다른 작업을 수행합니다.
232+
1. `1.js`를 로드합니다. 그 후 에러가 없으면,
233+
2. `2.js`를 로드합니다. 그 후 에러가 없으면,
234+
3. `3.js`를 로드합니다. 그 후 에러가 없으면 `(*)`로 표시한 줄에서 또 다른 작업을 수행합니다.
235235

236-
호출이 계속 중첩되면서 코드가 깊어지고 있습니다. 본문 중간중간 `...`로 표시한 곳에 반복문과 조건문이 있는 코드가 실제로 들어가면 관리는 특히나 더 힘들어질 겁니다.
236+
호출이 계속 중첩되면서 코드가 깊어지고 있네요. 본문 중간중간 `...`로 표시한 곳에 반복문과 조건문이 있는 코드가 들어가면 관리는 특히나 더 힘들어질 겁니다.
237237

238-
이를 '콜백 지옥(callback hell)' 혹는 '멸망의 피라미드(pyramid of doom)'라고 부릅니다.
238+
이렇게 깊은 중첩 코드가 만들어내는 패턴은 소위 '콜백 지옥(callback hell)' 혹은 '멸망의 피라미드(pyramid of doom)'라고 불립니다.
239239

240240
<!--
241241
loadScript('1.js', function(error, script) {
@@ -267,7 +267,7 @@ loadScript('1.js', function(error, script) {
267267

268268
따라서 이런 코딩 방식은 좋지 않습니다.
269269

270-
비동기로 처리해야 할 동작 각각을 독립적인 함수로 만들어 위와 같은 문제를 완화해 보도록 합시다. 아래와 같이 말이죠.
270+
각 동작을 독립적인 함수로 만들어 위와 같은 문제를 완화해 보도록 합시다. 아래와 같이 말이죠.
271271

272272
```js
273273
loadScript('1.js', step1);
@@ -299,12 +299,12 @@ function step3(error, script) {
299299
};
300300
```
301301

302-
새롭게 작성한 코드는 위에서 작성한 콜백 기반 스타일 코드와 동일하게 동작합니다. 다만, 각 동작을 분리해 최상위 레벨의 함수로 만들었기 때문에 깊은 중첩이 없습니다.
302+
어떤가요? 새롭게 작성한 코드는 각 동작을 분리해 최상위 레벨의 함수로 만들었기 때문에 깊은 중첩이 없습니다. 그리고 콜백 기반 스타일 코드와 동일하게 동작하죠.
303303

304-
이렇게 작성해도 문제는 없습니다만, 코드가 찢어진 종잇조각 같아 보이네요. 읽는 것도 어렵습니다. 눈을 이리저리 움직이며 코드를 읽어야 하죠. 코드에 익숙지 않아 눈을 어디로 옮겨야 할지 모르면 더욱더 불편합니다.
304+
그런데 이렇게 작성하면 동작상의 문제는 없지만, 코드가 찢어진 종잇조각 같아 보인다는 문제가 생깁니다. 읽는 것도 어려워지죠. 눈을 이리저리 움직이며 코드를 읽어야 합니다. 코드에 익숙지 않아 눈을 어디로 옮겨야 할지 모르면 더욱더 불편할 것입니다.
305305

306-
`step*`이라고 명명한 함수들은 '멸망의 피라미드'를 피하려는 용도만으로 만들었기 때문에 재사용도 할 수 없습니다. 연쇄 동작이 이뤄지는 코드 밖에선 아무도 이 함수를 재활용하지 않을 겁니다. 네임스페이스가 약간 복잡해졌습니다(namespace cluttering).
306+
게다가 `step*`이라고 명명한 함수들은 '멸망의 피라미드'를 피하려는 용도만으로 만들었기 때문에 재사용이 불가능합니다. 그 누구도 연쇄 동작이 이뤄지는 코드 밖에선 함수들을 재활용하지 않을 겁니다. 네임스페이스가 약간 복잡해졌네요(namespace cluttering).
307307

308-
더 나은 무언가가 필요한 상황입니다.
308+
지금쯤이면 더 나은 무언가가 필요하다는 생각이 강하게 들 겁니다.
309309

310-
운 좋게도, 멸망의 피라미드를 피할 수 있는 방법이 몇 가지 있습니다. 가장 좋은 방법 중 하나는 다음 챕터에서 설명할 '프라미스(promise)'를 사용하는 것입니다.
310+
운 좋게도, 멸망의 피라미드를 피할 방법이 몇 가지 있습니다. 가장 좋은 방법 중 하나는 다음 챕터에서 설명할 '프라미스(promise)'를 사용하는 것입니다.

0 commit comments

Comments
 (0)