자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백함수를 사용한다.
하지만 전통적인 콜백 패턴은 콜백헬로 인해 가독성이 나쁘고 에러의 처리가 곤란하며 여러개의 비동기 처리를 한번에 처리하는 데도 한계가 있다.
이 이유를 지금부터 살펴보자
이런 get 함수가 있다.
get 함수가 서버에 응답결과를 반환하게 할려면 어떻게 해야 할까?
여기서 get 함수는 비동기 함수라고 할 수 있다.
비동기 함수란 함수 내부에 비동기로 동작하는 코드를 포함한 함수를 말한다. 비동기 함수를 호출하면 함수 내부의 비동기로 동작하는 코드가 완료되지 않았다 해도 기다리지 않고 즉시 종료된다.
위의 코드의 실행과정을 분석해 보기전에 이벤트 루프에 대해 먼저 알아보자.
자바스크립트는 single threaded language 이다.
하지만 자바스크립트가 동작하고 있는 브라우저라는 프로그램 안에는 여러가지의 쓰레드가 들어있다. 그래서 웹 API들을 이용하면 멀티 쓰레딩이 가능하다.
webAPis(DOM API, setTimeout, setInterval, fetch,event listener)
그러면 webAPI를 통해서 등록한 콜백함수는 어떻게 동작할까?
예를 들어 setTimeout()이 호출된다고 가정해보자. setTimeout()이 호출되는 순간 setTimeout은 call stack에서 지워지고 웹 API는 타이머를 시작하게 된다.
<출처 - 드림코딩 강의>
타이머가 실행되고 있는 동안 타이머와 자바스크립트 엔진은 병렬적으로 실행이 되고 있다가. 시간이 끝나면 웹 APIs는 task queue에 콜백함수를 집어 넣는다.
Event Loop 가 Task Queue와 Call Stack을 관찰한다. 삥글삥글 돌다가 콜스택에 쌓여있는 일들이 수행할 때까지 감시하다가 텅텅 비면 Task Queue에 있는 아이를 Call Stack으로 데려온다.
비동기 함수 get이 호출되면 get함수의 실행 컨텍스트가 생성되고 실행 컨텍스트는 Call Stack에 푸시된다. 이후 함수 코드 실행과정에서 xhr.onload 이벤트 핸들러 프로퍼티에 이벤트 헨들러가 바인딩 된다.
get 함수가 종료하면 get 함수의 실행컨텍스트가 Call Stack에서 pop되고 곧바로 console.log가 호출된다. console.log의 실행 컨텍스트가 생성되어 stack에 push 된다.
xhr.onload 이벤트 핸들러는 load 이벤트가 발생하면 일단 task queue에 저장되어 대기하다가 , Call Stack이 비면 이벤트 루프에 의해 Call Stack으로 푸시되어 실행된다. 따라서 xhr.onload 이벤트 핸들러가 실행되는 시점에는 Call Stack이 빈 상태여야 하므로 console.log는 이미 종료된 이후다.
이처럼 비동기 함수는 비동기 처리 결과를 외부에 반환할 수 없고, 상위 스코프의 변수에 할당할 수도 없다.
따라서 비동기 함수의 처리 결과에 대한 후속처리는 비동기 함수 내부에서 수행해야 한다. 이때 비동기 함수를 범용적으로 사용하기 위해 비동기 함수에 비동기 처리 결과에 대한 후속 처리를 수행하는 콜백함수를 전달하는 것이 일반적이다.
문제점은 아래의 코드처럼 후속처리를 하는 비동기 함수가 비동기 처리결과를 가지고 또다시 비동기 함수를 호출하게 되면 매우 골치아퍼지게 된다. => Callback Hell
콜백패턴의 문제점 중 가장 심각한 건 에러처리가 곤란하다는 것이다.
아래의 코드를 보자.
비동기 함수 setTimeout이 호출되면 setTimeout 함수의 실행컨텍스트가 생성되어 Call Stack에 푸시되어 실행된다.
setTimeout은 콜백함수의 호출을 기다리지 않고 즉시 종료되 Call Stack에서 나온다. 이후 타이머가 만료되면 콜백함수가 Call stack이 비어졌을 때 이벤트 루프에 의해 push되어 실행된다.
에러는 호출자 방향으로 전파된다. 즉 Call Stack의 아래방향으로 전파된다. 하지만 setTimeout 함수의 콜백함수를 호출한 건 setTimeout 함수가 아니다. 따라서 setTimeout 함수의 콜백함수가 발생시킨 에러는 catch 블록에서 캐치되지 않는다.
이를 극복하기 위해 ES6에서는 프로미스가 도입이 되었다.
다음 글에서 프로미스를 살펴볼 것이다.