자바스크립트에는 이벤트루프가 없다

junamee·2021년 8월 14일
2

자바스크립트

목록 보기
4/11
post-thumbnail

👽불과 몇달 전의 생각들...

지금은 어느정도 이해가 되었지만,
몇달 전엔 이벤트 루프는 어디서 나온 애고,
자바스크립트가 일을 순차적으로 한다는데 왜 이 함수가 마칠 때까지 기다리지 않는거지?
의문 투성이였다. 뭐 지금도 완벽히 안다고는 할 수 없지만 그동안 제일 헷갈렸던 고민은 다음과 같았다.

자바스크립트는 싱글스레드라며?
그럼 순서대로 태스크를 처리해야하는데
어떻게 한번에 여러개를 실행하는거지?

동기적인 자바스크립트라면
비동기를 컨트롤하는 Promise와 async - await와 같은 것들은 왜 존재하는거야?

어떻게든 정리해보면
자바스크립트는 동기적인데 비동기적으로 처리되는 일을 동기적으로 나타내기 위해
Promise와 async-await가 나왔나봐!?


🤔알고 가야할, 가정하고 가야할 것들

  • 자바스크립트는 싱글스레드 기반의 언어이다.
    • "Run To Completion" 함수의 실행이 끝날 때까지 다른 작업이 중간에 끼어들지 못한다.
  • 자바스크립트 엔진
    • 자바스크립트 코드를 실행하는 프로그램 또는 인터프리터, 대체적으로 웹 브라우저에서 사용된다.
    • V8은 자바스크립트 엔진 중의 하나이다. (오픈 소스. 덴마크에서 구글이 개발. 구글 크롬의 일부)
    • 단일 호출 스택(Call Stack)을 사용
  • 비동기 요청, 동시성 처리는 브라우저, Node.js가 담당한다. (자바스크립트의 일이 아니다)
    • 비동기 함수인 setTimeout, fetch는 Web API영역이다.
    • 자바스크립트언어를 사용하면서 비동기 요청이 필요할 때, WebAPI를 차용해 쓰고 있는 것이다.
    • EX) nodeJS에서 libuv라이브러리를 사용하고, 이것이 이벤트루프를 제공한다. 자바스크립트 엔진이 비동기작업을 하기 위해 Node.js API를 호출하고 넘겨진 콜백이 libuv라이브러리를 통해 이벤트 루프가 실행된다.
  • 결론 :
    자바스크립트는 싱글스레드고 자바스크립트 엔진이 단일 호출스택을 제공한다.
    비동기 작업을 위해서는 자바스크립트가 구동되는 외부 환경(런타임) Node.js나 브라우저의 API를 통해 여러개의 스레드를 처리해야하며, 자바스크립트 엔진의 단일호출스택과 상호연동되기 위한 장치인 이벤트 루프를 런타임으로 부터 제공받아 사용하게 된다.

🗽동기적인 자바스크립트에서 비동기가 필요한 이유

위의 정리처럼 자바스크립트가 동기적 처리를 진행하면 사실 어려울 게 없다.
코드를 한 줄 씩 읽고 처리하면 되는데, 왜 복잡하게 비동기를 사용하게 되었고,
비동기를 동기적으로 연출(?)해 내야하는 상황이 발생한 걸까?

비동기가 없다고 생각해 보자.
한 사이트에 접속할 때 여러 사이트로부터 데이터를 요청하고 js, html 파일 등이 필요한데,
여러 개의 파일 요청을 보내지 못하고, 한번에 하나의 요청만 보내지고 결과가 와야만 다음 요청을 보낼 수 있는 상황인 것이다.

파일 하나의 응답이 매~우 오래 걸린다면?
심지어 html을 그려내는 파일이 가장 늦게 요청되었다면?
js파일 읽어와도 화면을 그려내는 응답이 올 때까지 무한~정 기다려야하고
우리는 그 긴 시간을 아무것도 없는 하얀 화면을 보고 있게 되는 것이다.

또한, UI Blocking이 일어나는 것 뿐 아니라, 콜 스택에 많은 태스크들이 쌓이면 브라우저는 응답을 장시간 멈추게 되고 응답이 올 때까지 대기할 것인지, 웹페이지를 종료 시킬 것인지에 대한 에러를 반환한다.


❔ 단일 호출 스택, 태스크 큐, 이벤트 루프

● 단일 호출 스택 Call Stack

요청이 들어올 때마다 해당 요청을 순차적으로 호출 스택에 담는다.
코드를 읽으며 순서대로 스택에 쌓는다. 그리고 함수를 실행하면 해당 함수를 스택에서 지워버린다.

콜스택이 눈에 보이지 않아 그저 자바스크립트 엔진 어딘가에서 담당하고 있다고 인지해야했다.
그런데 알고보니 콜스택은 너무나도 자주 접했던 아이였다.

  • 에러를 만났을 때!
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*
foofoo
barbarbar
startstartstartstart
globalglobalglobalglobal

foo에서 에러를 던지며 함수가 더 실행되지 못한다.
이 때 익숙한(?) 빨간 에러화면이 뜨는데...

평소 에러 유형이 무엇인지만 파악하고 넘어갔는데,
2번째 줄부터 at을 보면 순차적으로 콜스택의 순서와 동일함을 알 수 있다.


크롬 개발자도구의 디버깅 툴에서도 CallStack을 확인할 수 있다.
start-bar-foo 순서가 동일함을 확인할 수 있다.

  • 만약 실수로 재귀적으로 함수를 호출한 경우에는?

    에러메시지도 말 그대로 'Maximum call stack size exceeded' 이다. 이 경우는 실제 콜스택 사이즈를 초과하여 함수가 콜 스택에 쌓여버린 경우이고, 브라우져는 이를 무한정 쌓아올릴 수 없으니 에러를 던져 실행을 멈추게 한 것이다.

이렇게 자주 접했던게 콜스택의 존재라니...! (
이쯤되면 자바스크립트 엔진에서 콜스택을 지원함을 알 수 있다 하하하


● 태스크 큐 Task Queue

콜백 함수들이 대기하는 큐(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)종료
setTimeoutconsole.logconsole.logconsole.log
setTimeoutfoofoofoobazbaz 2
globalglobalglobalglobalglobalglobalglobal

*태스크 큐

(1)(2)
bazbaz 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) 콜스택이 모두 비워지고, 태스크 큐에도 남아있는 태스크가 없어 모든 작업을 종료하게 된다.

그리고 스택이 비워짐을 확인하고 태스크 큐의 태스크를 활성화시키는 역할을 '이벤트 루프' 가 하고 있는 것이다.

● 이벤트 루프 Event Loop

호출 스택이 비워질 때마다 큐에서 콜백 함수를 꺼내와서 실행하는 역할
위의 내용에 이어서 이벤트루프의 동작을 정리해보면 다음과 같다.

  • 비동기 API는 비동기적으로 실행할 콜백함수를 태스크 큐에 넣는다.
  • 콜스택이 비워지면(실행중인 태스크가 없는 경우) 태스크 큐의 첫번째 요소를 꺼내 콜스택에 집어넣는다.

🎐마무리

제목은 이벤트루프가 대문작만하게 들어갔지만,
설명을 정리하고 나니 콜스택과 태스크큐의 흐름이다. 뭐 이벤트 루프가 이들을 가능하게 해주는 역할이니 이벤트루프 설명이라고 볼 수 있겠다.

개인적으로는 자바스크립트가 동기적인데 이벤트루프는 비동기적인 일처리 가능하게 해주니 이해가 안갔었다. 이벤트루프를 자바스크립트의 속성으로 인지하여 생긴 오해였다.

즉 이번 정리를 통해 알게 된 바는 자바스크립트에는 이벤트루프가 없다는 것. 이벤트 루프는 런타임환경에서 제공하는 라이브러리이다. 라고 이해하니 대부분의 내용이 이해가 되었다.


profile
아티클리스트 - bit.ly/3wjIlZJ

0개의 댓글