
지난번 포스팅에서 javascript 엔진이 이벤트루프를 통해 어떻게 비동기적으로 처리되는지 봤다.
비동기(asynchronous) - 1 (Event-Loop)
Event-Loop 동작 예시로 아래와 같은 코드를 살펴봤었다.
console.log('Hi');
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
위 코드는 Call Stack에서 브라우저가 제공하는 비동기적 함수가 실행되면 Web-API를 호출하고, Callback Queued에서 대기하다가 Call Stack이 비워지면 Event-Loop에 의해 다시 Call Stack으로 이동해 함수가 실행되었다.
그래서 아래 그림과 같이 Browser Console에는 코드가 써져있는 순서와는 다르게 결과가 표시됐다.

브라우저에서 제공하는 비동기적인 함수는 위의 예시처럼, 발생한 이벤트나 설정한 타이머에 따라 요청과 결과의 시점이 다르기 때문에 언제 발생할지 예측할 수 없다.
그 말은 우리가 그 순서를 제어할 수도 없다는 것을 의미한다.
이런 비동기적인 함수의 순서를 제어하고 싶을 때 쓰는 것이 Callback 함수다.
참고로 지난번 봤던 브라우저에서 제공하는 setTimeout() 함수도 인자(파라미터)로 함수를 받았었다.
setTimeout(함수, 시간) : 인자로 설정한 시간(타이머)이 지나면 인자로 설정한 함수를 실행.
여기서 setTimeout()에 인자로 들어가는 함수도 Callback 함수다.
비동기 처리의 순서를 제어할 수 없는 경우의 예시를 먼저 보자.
const printString = (string) => {
setTimeout(
() => {
console.log(string)
},
Math.floor(Math.random() * 100) + 1
)
}
const printAll = () => {
printString("A");
printString("B");
printString("C");
}
PrintAll();
간단히 코드를 설명하면
printString(string) : setTimeout을 실행시켜 timer에 랜덤한 숫자(시간)를 세팅하고, 세팅된 시간이 지나면 콘솔에 인자로 받은 string을 출력한다.
printAll() : 차례대로 위에서 정의한 printString("A"), printString("B"), printString("C")를 실행한다.
이 경우 timer의 시간이 랜덤하게 설정되므로 printAll을 실행시켰을 때, 콘솔에 문자열 "A", "B", "C"가 함수를 실행할때마다 랜덤한 순서로 출력된다.
그럼 timer가 몇초이든 "A", "B", "C"를 순서대로 출력하고 싶다면..?
이때 Callback을 활용할 수 있다.
위에서 봤던 printAll() 함수를 실행시켰을 때,
setTimeout에 설정된 시간과 관계없이 A, B, C 순서대로 출력되게 하고 싶다면 Callback을 활용하면 된다.
const printString = (string, callback) => {
setTimeout(
() => {
console.log(string);
callback();
},
Math.floor(Math.random() * 100) + 1
)
}
const printAll = () => {
printString("A", () => {
printString("B", () => {
printString("C", () => {
})
})
});
}
PrintAll();
위 예시 코드를 보면 아까와 다르게 printString()라는 함수에 추가로 callback이라는 인자를 받고 있다. 그리고 setTimeout()의 timer가 지나면 console에 string이라는 인자로 받은 것이 출력되고 callback이 함수로써 실행된다.
printAll()이라는 함수를 보면
1. printString("A", callback1)
결과적으로 printAll()이라는 함수를 실행시키면 몇번을 실행시켜도 console에는 순서대로 "A", "B", "C"가 출력되게 된다.
이런 Callback 처리로 인해 아까와는 다르게 printString()이라는 비동기 함수를 순차적으로 제어할 수 있는 것이다.
다른 함수에 인자(파라미터)로 넘겨주고, 때가 되면 나중에 호출(callback) 받는 다는 것이다.
즉, 여러번의 비동기 호출이 이루어지는데 각 처리는 비동기로 이루어지나, 각 비동기호출간의 실행순서는 동기적으로 제어 가능하다.
Callback은 훌륭하지만 문제점도 많다..

콜백장풍짤..(출처:구글)
위 짤처럼..남발하면 그냥 가독성이 너무 떨어진다. 가독성이 떨어지면 스코프 구분도 힘들고 로직을 이해하는데도 힘들다. 또한 이 때문에 디버깅과 유지보수 등도 힘들어진다.
이 때문에 이러한 무분별한 Callback의 남발을 Callback hell이라고 부른다.
그럼 이러한 callback을 어떤식으로 더 깔끔하게 효율적으로 개선할 수 있을까?
javascript는 이러한 문제점을 개선하기 위해 몇가지 강력한 도구를 또 제공한다.
바로 Promise와 async인데 이는 이후 포스팅에서 다루도록 하겠다.