지금은 어느정도 이해가 되었지만,
몇달 전엔 이벤트 루프는 어디서 나온 애고,
자바스크립트가 일을 순차적으로 한다는데 왜 이 함수가 마칠 때까지 기다리지 않는거지?
의문 투성이였다. 뭐 지금도 완벽히 안다고는 할 수 없지만 그동안 제일 헷갈렸던 고민은 다음과 같았다.
자바스크립트는 싱글스레드라며?
그럼 순서대로 태스크를 처리해야하는데
어떻게 한번에 여러개를 실행하는거지?
동기적인 자바스크립트라면
비동기를 컨트롤하는 Promise와 async - await와 같은 것들은 왜 존재하는거야?
어떻게든 정리해보면
자바스크립트는 동기적인데 비동기적으로 처리되는 일을 동기적으로 나타내기 위해
Promise와 async-await가 나왔나봐!?
위의 정리처럼 자바스크립트가 동기적 처리를 진행하면 사실 어려울 게 없다.
코드를 한 줄 씩 읽고 처리하면 되는데, 왜 복잡하게 비동기를 사용하게 되었고,
비동기를 동기적으로 연출(?)해 내야하는 상황이 발생한 걸까?
비동기가 없다고 생각해 보자.
한 사이트에 접속할 때 여러 사이트로부터 데이터를 요청하고 js, html 파일 등이 필요한데,
여러 개의 파일 요청을 보내지 못하고, 한번에 하나의 요청만 보내지고 결과가 와야만 다음 요청을 보낼 수 있는 상황인 것이다.
파일 하나의 응답이 매~우 오래 걸린다면?
심지어 html을 그려내는 파일이 가장 늦게 요청되었다면?
js파일 읽어와도 화면을 그려내는 응답이 올 때까지 무한~정 기다려야하고
우리는 그 긴 시간을 아무것도 없는 하얀 화면을 보고 있게 되는 것이다.
또한, UI Blocking이 일어나는 것 뿐 아니라, 콜 스택에 많은 태스크들이 쌓이면 브라우저는 응답을 장시간 멈추게 되고 응답이 올 때까지 대기할 것인지, 웹페이지를 종료 시킬 것인지에 대한 에러를 반환한다.
요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담는다.
코드를 읽으며 순서대로 스택에 쌓는다. 그리고 함수를 실행하면 해당 함수를 스택에서 지워버린다.
콜스택이 눈에 보이지 않아 그저 자바스크립트 엔진 어딘가에서 담당하고 있다고 인지해야했다.
그런데 알고보니 콜스택은 너무나도 자주 접했던 아이였다.
function foo() { //(4)
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() { //(3)
foo();
}
function start() { //(2)
bar();
}
start(); //(1)
머리로 그려본 가상의 콜스택은 아래와 같다.
(1) | (2) | (3) | (4) |
---|---|---|---|
throwError* | |||
foo | foo | ||
bar | bar | bar | |
start | start | start | start |
global | global | global | global |
foo에서 에러를 던지며 함수가 더 실행되지 못한다.
이 때 익숙한(?) 빨간 에러화면이 뜨는데...
평소 에러 유형이 무엇인지만 파악하고 넘어갔는데,
2번째 줄부터 at을 보면 순차적으로 콜스택의 순서와 동일함을 알 수 있다.
크롬 개발자도구의 디버깅 툴에서도 CallStack을 확인할 수 있다.
start-bar-foo
순서가 동일함을 확인할 수 있다.
이렇게 자주 접했던게 콜스택의 존재라니...! (
이쯤되면 자바스크립트 엔진에서 콜스택을 지원함을 알 수 있다 하하하
콜백 함수들이 대기하는 큐(FIFO) 형태의 배열
함수를 즉시 실행하고 종료하는 경우엔 콜 스택에서 모두 해결할 수 있다.
콜 스택 최상위 태스크를 실행하고 종료한다. 태스크 큐가 사용되는 경우는 다음과 같다.
function foo() {
setTimeout(baz2, 0) //(3)
console.log('foo!'); //(4)
}
function baz() {
console.log('baz!'); //(5)
}
function baz2(){
console.log('baz2!!') // (6)
setTimeout(baz, 10); // (1)
foo(); //(2)
*콜스택
(1) | (2) | (3) | (4) | (5) | (6) | 종료 |
---|---|---|---|---|---|---|
setTimeout | console.log | console.log | console.log | |||
setTimeout | foo | foo | foo | baz | baz 2 | |
global | global | global | global | global | global | global |
*태스크 큐
(1) | (2) |
---|---|
baz | baz 2 |
setTimeout을 콜 스택에 바로 집어넣고 실행한다. 10ms가 지나면 브라우저 타이머가 setTimeout의 콜백함수인 baz를 바로 실행하지 않고 태스크 큐에 넣어둔다.
(🎈자바스크립트가 싱글 스레드임을 감안하여, 현재 진행중인 태스크를 완료한 후에 다음 작업을 해야하므로 대기명단에 넣어두었다고 이해하였다.)
때문에, 정확하게 10ms가 지나고 태스크 처리하는 게 아니다. 시간차이가 발생하게 된다. 또 주의깊게 볼 부분은 setTimeout(baz2, 0)
이다. 이 라인만 보면 'baz2를 0초 후, 즉시 실행해라'라고 이해할 수 있지만 정확히는 바로 실행하는게 아니고 0초 후, 바로 태스크큐에 넣는 것이다.
즉 '*초 후에 이 실행할 함수를 등록한다' 라는 표현이 맞다.🦋
태스크 큐에 넣어둔 작업을 처리할 시점은 현재 작업 중인 태스크가 종료되는 시점이다. 즉, 콜 스택이 모든 함수를 처리하고 비어지는 시점이다.
위의 경우에는
1) foo함수가 종료되고 콜스택이 비어진다. (콜스택_4)
2) 태스크큐(1)번 baz가 태스크큐에서 나온다.
3) baz가 콜스택 최상단에 담겨진 후에 실행된다. (콜스택_5),
4) baz가 종료되어 콜스택이 비게 되면 태스크 큐에 대기하던 (2)번 태스크 baz 2가 콜 스택에 들어가 실행된다.(콜스택_6)
5) 콜스택이 모두 비워지고, 태스크 큐에도 남아있는 태스크가 없어 모든 작업을 종료하게 된다.
그리고 스택이 비워짐을 확인하고 태스크 큐의 태스크를 활성화시키는 역할을 '이벤트 루프' 가 하고 있는 것이다.
호출 스택이 비워질 때마다 큐에서 콜백 함수를 꺼내와서 실행하는 역할
위의 내용에 이어서 이벤트루프의 동작을 정리해보면 다음과 같다.
제목은 이벤트루프가 대문작만하게 들어갔지만,
설명을 정리하고 나니 콜스택과 태스크큐의 흐름이다. 뭐 이벤트 루프가 이들을 가능하게 해주는 역할이니 이벤트루프 설명이라고 볼 수 있겠다.
개인적으로는 자바스크립트가 동기적인데 이벤트루프는 비동기적인 일처리 가능하게 해주니 이해가 안갔었다. 이벤트루프를 자바스크립트의 속성으로 인지하여 생긴 오해였다.
즉 이번 정리를 통해 알게 된 바는 자바스크립트에는 이벤트루프가 없다는 것. 이벤트 루프는 런타임환경에서 제공하는 라이브러리이다. 라고 이해하니 대부분의 내용이 이해가 되었다.