어쨌든 이벤트 루프는 무엇입니까?
자바스크립트는 싱글스레드이다. 즉, '싱글 콜스택을 가진다', '하나의 프로그램은 한 번의 하나의 코드만 실행가능하다', '한 번에 하나의 일만 처리할 수 있다'. 와 동일한 의미이다. 그렇다면 JS로 구성된 웹페이지는 어떻게 페이지를 불러오면서 spinner와 같은 UI 애니메이션을 보여주며 비동기적 처리가 가능한 것일까? 그 답은 브라우저에 있다.
1. JavaScript Engine
V8와 같은 자바스크립트 엔진은 위와 같이 힙영역과 스택영역으로 구성되어 있다. 이 부분은 분명히 싱글스레드이다. 따라서 함수호출(처리할 일)이 생길 때마다 하나의 스택에 일렬로 쌓이이며 나중에 쌓인 함수부터 차례대로 처리된다.
자바스크립트 엔진의 또 다른 특징은 setTimeout, DOM, HTTP 요청 관리코드 들이 존재하지 않는다는 것이다. 자바스크립트의 대표적인 특징이 비동기라는 것인데 setTimeout이 존재하지 않는다는 것은 이상하다. setTimeout의 정체는 대체 무엇일까?
2. Browser
브라우저는 자바스크립트 엔진 뿐만 아니라 WebAPI, Callback(Task) Queue, Event Loop를 포함하고 있다. 이 중 WebAPI는 비동기 작업을 관리하는 영역이다. 즉, 자바스크립트 런타임은 한 번에 하나의 일만 처리할 수 있지만 브라우저의 Web API를 통해 비동기적 처리를 함으로써 동시에 여러 작업을 하는 것처럼 보이는 것이다. 따라서 비동기적 처리인 setTimout, DOM 다루기, Ajax 요청은 브라우저가 제공하는 API인 것이다.
3. Blocking
블로킹이란 네트워크 요청, 이미지 프로세싱과 같이 실행시간이 긴 함수가 스택에 남아있어서 그 일을 처리하기 전까지 UI 업데이트 등 다른 동작을 진행할 수 없는 상태이다. 웹이 동기적으로만 작업을 처리한다면 자바스크립트의 경우 싱글 스레드이기 때문에 데이터 요청과 UI 렌더링과 같은 2가지 작업을 동시에 할 수 없으므로 데이터 요청작업이 끝나야 UI 렌더링 작업을 진행할 수 있다. 이 때 데이터 요청에 2분이 소요되었다면, 브라우저는 2분 동안 데이터 요청 처리를 하느라 UI 렌더링을 수행할 수 없다. 즉, 데이터 요청에 의해 블로킹이 된 것이다.
웹에서 네트워크 요청, 이미지 프로세싱과 같이 블로킹을 발생시킬 수 있는 오래 걸리는 작업을 비동기적으로 수행시킨다면 어떻게 될까? 이 경우 비동기적 작업은 바로 스택에 들어가는 것이 아니라 Web API를 통해 처리가 진행된다. 그리고 요청이 끝났을 때 요청에 붙어있는 콜백이 스택에 추가되는 것이다. 따라서 Web API의 처리가 되는 동안 UI 렌더링을 하여 데이터 요청의 콜백함수가 수행되기 직전까지 BusyIndicator, Spinner와 같은 기다림을 의미하는 UI를 보여줄 수 있는 것이다.
4. 브라우저의 비동기요청 처리
다음의 코드가 브라우저에서 어떻게 동작하는지 살펴보자.
console.log("111"); //1번 코드
setTimeout(function cb() { //2번 코드
console.log("222");
}, 5000);
console.log("333"); //3번 코드
1번 코드가 실행되었을 때 스택의 상황은 위와 같다. 스택에는 먼저 main함수가 올라가고 그 위로 1번 코드가 쌓인다. 1번 코드가 실행되면 스택에서 1번 코드는 사라진다. 이 때 브라우저의 WebAPIs와 Callback Queue, Event Loop는 비어있다.
2번코드가 실행되면 setTimeout 역시 스택에 쌓인다. 이 때 setTimeout은 비동기 함수이므로 브라우저의 API인 timer를 호출하여 5초의 카운트다운을 시작한다.
3번 코드가 실행되기 직전의 stack은 main()만 존재한다. 이 위에 3번 코드가 쌓이고 실행이 완료되면 3번 코드가 스택에서 사라진다. 이 때 3번 코드가 끝났다는 것은 main()의 모든 코드가 실행이 완료되었다는 의미이므로 main()도 스택에서 제거된다. 즉, 스택이 비어있게된 것이다.
스택에 존재하는 모든 작업이 완료된 현재 상태는 위의 그림과 같다. 동기적 작업이 없으므로 스택은 비어있고, 브라우저의 timer가 호출되어 5초의 카운트다운을 진행하고 있다. 5초의 카운트다운이 종료되면 어떤 일이 발생할까?
5초가 지나면 setTimeout에 연결된 콜백함수가 콜백 큐로 이동한다. 즉, 콜백 큐는 비동기 호출작업에 연결되어 있는 콜백 작업이 스택에 올라가기 전까지 대기하는 장소이다. 콜백 큐로 이동한 콜백 작업은 스택이 비어있는 상태일 때 이벤트 루프에 의해 스택에 순차적으로 올라간다. 즉, 이벤트 루프는 콜백큐와 스택을 관찰하며 스택이 빈 상태이고 콜백 큐에 작업이 있으면 콜백 큐에서 작업을 꺼내 스택으로 옮겨주는 역할을 하는 것이다. 스택으로 이동된 콜백은 처리가 끝나면 역시 스택에서 사라진다.
자바스크립트에서 가끔 setTimeout(function, 0)이라는 코드를 볼 수 있다. 이 코드의 의미는 0초 후에 function을 실행하라는 의미가 아니라 stack이 다 비었을 때 function을 실행하라는 의미인 것이다.
위 영상을 보면서 브라우저에서 비동기처리를 어떻게 수행하고 있는 것인지 원리를 이해하는데 정말 많은 도움이 되었다. 또한 아래의 블로그 글이 동기와 비동기, 블로킹과 논블로킹의 개념이해에 도움이 되었다.