[자바스크립트] 비동기 처리의 원리
자바스크립트 코드를 짜다보면 의아할 때가 있다. 내가 짜놓은 코드가 순서대로 돌아가지 않는 순간 말이다. 다음 코드를 보고 콘솔로그의 순서를 예측해보자.
var exam = function(callback){
console.log("exam called");
setTimeout(function(){
console.log("setTimeout called");
if(typeof callback === "function"){
callback();
} else {
console.log("callback is not function");
}
},3000);
console.log("end of exam");
}
exam(function(){
console.log("callback called");
})
통상적인 방식으로 생각해보자. 예컨대 자바에서 스택에 함수가 차곡차곡 쌓이는 것처럼. 그러면 일단 exam 이 거고, exam called 가 찍힐 것이다. 그 다음엔 setTimeout 이 호출되고 3초의 대기 시간을 거친 뒤 setTimeout called! 가, 그 다음 callback called , 마지막으로 end of exam 이 콘솔에 찍힐 것이다. 번호로 순서를 붙여보자면 다음과 같다.
var exam = function(callback){
console.log("exam called"); // ---> ①
setTimeout(function(){
console.log("setTimeout called"); // ---> ②
if(typeof callback === "function"){
callback();
} else {
console.log("callback is not function");
}
},3000);
console.log("end of exam"); // ---> ④
}
exam(function(){
console.log("callback called"); // ---> ③
})
Colored by C
그러나 실제 콘솔 로그는 다음과 같이 찍혀있는 것을 확인할 수 있다.
어째서인가. 예측한대로라면, 프로그램이 순서대로 돌아간다면 end of exam 은 분명 마지막에 찍혀야 하는데 왜 난데없이 두번째로 튀어나오는 것일까. 비밀은 자바스크립트의 비동기 처리 방식에 있다. 이를 설명하기 위해서는 먼저 자바스크립트가 어떤 환경에서 어떻게 돌아가는지, 그 작동 원리에 대해서 알아볼 필요가 있다.
자바스크립트와 이벤트 루프
자바스크립트는 기본적으로 싱글 스레드 기반의 언어이다. 다시말해 하나의 스레드만으로 모든 작업을 처리한다는 뜻이다. 그런데 실제로는 동시에 여러가지 작업을 처리해야하는 일이 비일비재하다. 브라우저의 스크롤을 내려도 화면 상단의 애니메이션이 계속 움직이는 등 여러가지 일이 동시에 일어난다. 그렇다면 이런 동시성을 어떻게 싱글 스레드로 처리하는 것일까?
먼저 자바스크립트가 돌아가는 브라우저의 환경은 다음과 같다.
http://meetup.toast.com/posts/89
비동기 처리는 이렇게 자바스크립트 엔진을 구동하는 브라우저에서 처리된다. 자바스크립트 엔진의 호출스택은 단 하나뿐인데, 현재 스택 내부에 쌓여있는 작업을 모두 처리한 이후에야 다른 작업을 쌓는다. 자바스크립트가 싱글 스레드라는 특징은 사실 이 호출스택과 깊은 연관이 있는 것이다.
var exam = function(callback){
console.log("exam called");
setTimeout(function(){
console.log("setTimeout called");
if(typeof callback === "function"){
callback();
} else {
console.log("callback is not function");
}
},3000);
console.log("end of exam");
}
exam(function(){
console.log("callback called");
})
일단 코드를 실행하면 호출스택에 exam 함수가 쌓인다. 예상과 같이 처음에는 exam called 가 찍힌다. 그 다음 setTimeout (이 함수는 Web APIs 내부에 정의되어 있다)가 호출된다. 그런데 setTimeout 함수는 호출스택에 올라가자마자 브라우저에 타이머 이벤트를 요청한 후 바로 스택에서 제거된다.
이벤트 루프는 end of exam 을 찍는 작업을 호출스택에 올린다. 하지만 3초가 지난 후, setTimeout called 가 바로 스택에 올라가지는 않는다. 일단 3초가 지나면 브라우저의 타이머는 이를 테스크큐에 넣는다. 그리고 스택이 비게 되면 그제서야 이벤트 루프에 의해 데스크큐에 있던 작업이 스택으로 넘어가서 처리되는 것이다. 이 과정에서 호출스택의 변화를 그림으로 표현해보면 다음과 같다.
중요한 것은 이벤트 루프와 테스크큐의 역할이다. 이벤트 루프는 테스크큐와 호출스택을 반복적으로 확인한다. 그리고 스택이 비어있다면 테스트큐에서 대기중인 테스크를 하나 가져와 호출스택에 넣고 실행한다.
setTimeout 함수의 예를 보며, 3초가 지난뒤 setTimeout called! 가 바로 스택에 올라가지 않는다고 했었다. 이는 일단 테스크큐에 올라간다. 이런식으로 자바스크립트의 비동기 API들은 작업이 완료되면 콜백함수를 테스크큐에 넣는다. 이 콜백의 처리는 이벤트 루프가 담당하는 것이다.
이렇게 간략한 설명으로 자바스크립트의 비동기 처리 방식을 모두 이해하는데는 무리가 있다. 하지만 자바스크립트 개발을 한다면 최소한 이정도는 알아야 한다는 데에 초점을 맞추어 글을 작성해 보았다. 아래의 참고글을 보며 많은 도움을 받았으니 시간이 난다면 한번 읽어보도록 하자.
참고