1. 콜백
* 소개
- 실무에서 비동기 동작처리를 할 때 생기는 문제를 먼저 살펴보자.
- 대표적인 비동기 동작으로 스크립트나 모듈을 로딩하는 것이 있다.
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
- 위는 src에 있는 스크립트를 불러와서 다큐먼트에 추가하는 함수이다. 이 안에는 우리가 사용하고 싶은 harryFunc라는 함수가 들어있다고 가정하자.
loadScript('/harry/script.js');
harryFunc();
- 위에서 정의한 함수를 사용하여 스크립트를 불러오고 함수를 실행했다. 그러나 안타깝게도 에러가 발생한다.
- 이유는 브라우저가 스크립트를 읽어올 수 있는 시간이 충분히 확보되지 못했기 때문이다. 물론 언젠가 스크립트 로딩은 완료되겠지만 이미 함수호출은 지나갔다.
- 로딩이 완료된 후 함수나 변수를 사용하려면 콜백함수가 필요하다.
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('/harry/script.js', function() {
harryFunc();
//...
});
- loadScript의 두 번째 인수에 콜백함수를 추가했다. 콜백 함수는 스크립트 로드가 끝나면 실행된다.
- 따라서 새롭게 불러올 스크립트의 함수나 변수를 콜백 함수 안에서 호출하면 원하는 대로 외부 스크립트를 사용할 수 있다.
* 콜백 속 콜백
- 위의 예시에서는 하나의 스크립트를 불러왔다. 만약 여러 개의 스크립트를 순차적으로 불러오려면 어떻게해야할까.
loadScript('/harry/script.js', function(script) {
loadScript('/harry/script2.js', function(script) {
loadScript('/harry/script3.js', function(script) {
// ...
});
})
});
- 위와 같이 중첩 콜백을 만들면 바깥에 위치한 loadScript가 완료된 후, 안쪽으로 하나씩들어갈 것이다.
- 그러나 위처럼 콜백이 중첩되면 불러올 스크립트가 늘어날수록 오른쪽으로 길어진다. 이 문제는 3.콜백 지옥 에서 다루자.
2. 에러 핸들링
- 위에서 loadScript는 에러를 전혀 처리할 수 없다. 하지만 브라우저에서 로딩은 얼마든지 실패할 수 있으므로 콜백 함수에 에러 핸들링 기능을 추가해보자.
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error('로딩 실패!'));
document.head.append(script);
}
- 위와 같이 작성하면 loadScript가 로딩성공시 callback(null, script)을 호출하게 된다. 실패하면 callback(error)을 호출한다.
loadScript('/harry/script.js', function(error, script) {
if (error) {
// 에러 처리
} else {
// 로딩 성공
// ...
}
});
- 위와 같이 에러를 처리하는 패턴을 '오류 우선 콜백(error-first-callback)'이라 부른다.
- 오류 우선 콜백은 다음 관례를 따른다.
1. callback의 첫 번째 인수는 에러를 위해 남겨둔다. 에러가 발생하면 이 인수를 이용해 callback(err)이 호출된다.
2. 두 번째 이후 인자는 에러가 발생하지 않을 때 사용하기 위한 인자이다.
3. 콜백 지옥
- 위에서 콜백이 중첩될수록 오른쪽으로 길어지는 문제가 있다고 했다. 에러처리까지 있다면 더 길어질 것이다.
loadScript('/harry/script.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('/harry/script2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('/harry/script3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
}
});
}
})
}
});
- 위를 살펴보면 이렇다. script.js를 로드한다. 에러가 없으면 script2.js를 로드한다. 에러가 없으면 script3.js를 로드한다.
- 호출이 계속 중첩되면서 코드가 깊어지고 있다. 여기서 반복문이나 조건문이 들어가면 관리가 너무 힘들 것이다.
- 위처럼 깊은 중첩 코드가 만들어내는 패턴을 소위 '콜백 지옥(callback hell)' 또는 '멸망의 피라미드(pyramid of doom)'이라 한다.
- 위와 같은 코딩 방식을 피하기 위해 각 동작을 독립적인 함수로 만들어 문제를 해결해보자.
loadScript('/harry/script.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('/harry/script2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('/harry/script3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...
}
};
- 위처럼 작성하면 중첩없이 콜백 기반 스타일 코드와 동일하게 동작하게 할 수 있다.
- 그러나 위 코드는 코드를 읽으려면 이리저리 움직여서 읽어야하고, 각 step함수는 콜백지옥을 피하기위한 용도로만 만들어져서 재사용도 불가능하다.
- 위 문제를 해결하기위한 가장 좋은 방법은 다음 챕터의 '프라미스'이다.
참고
모던 자바스크립트 튜토리얼
'Language > JavaScript' 카테고리의 다른 글
<자바스크립트> 프라미스 체이닝과 에러 (0) | 2021.04.23 |
---|---|
<자바스크립트> 프라미스 (0) | 2021.04.21 |
<자바스크립트> try catch & 에러 (0) | 2021.04.16 |
<자바스크립트> 클래스의 기본과 상속 (0) | 2021.04.14 |
<자바스크립트> 프로토타입 (0) | 2021.04.12 |