본문 바로가기
Development/JavaScript

자바스크립트의 이벤트 루프

by 개발자 데이빗 2022. 2. 19.

들어 가기 전

브라우저의 구성

Web APIs

Event Table: 특정 event가 발생했을 때 어떤 callback 함수가 호출되야 하는지를 알고 있는 자료구조 

 

자바스크립트 런타임 환경의 시각적 표현

스택 (Call Stack)

실행된 코드 환경을 저장하는 자료구조

함수를 호출할때 함수의 인자와 지역 변수를 포함하는 프레임이 생성되어 스택에 push 된다.

 

객체는 힙에 할당된다.

힙은 메모리의 큰 영역을 지칭하는 용어이다.

메모리 할당이 발생하는 곳

 

JavaScript의 런타임은 메시지 큐, 즉 처리할 메시지의 대기열을 사용한다.

각각의 메시지에는 메시지를 처리하기 위한 함수가 연결되어 있다.

이벤트 루프 임의 시점에 런타임은 대기열에서 가장 오래된 메시지부터 큐에서 제거하며 처리한다.

이때 매개변수로 해당 메시지를 제공한다.

함수 호출로 인해 새로운 스택프레임도 생성된다.

 

함수처리는 스택이 다시금 비워질 때까지 계속된다.

 

이벤트 루프

MDN 문서의 이벤트 루프 정의

https://developer.mozilla.org/ko/docs/Web/JavaScript/EventLoop

 

이벤트 루프 - JavaScript | MDN

JavaScript의 런타임 모델은 코드의 실행, 이벤트의 수집과 처리, 큐에 대기 중인 하위 작업을 처리하는 이벤트 루프에 기반하고 있으며, C 또는 Java 등 다른 언어가 가진 모델과는 상당히 다릅니다.

developer.mozilla.org

이벤트 루프는 이 기능을 구현할 때 보통 사용하는 방식에서 그 이름을 얻었으며, 대략 다음과 같은 형태입니다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}
 
queue.waitForMessage() 함수는 현재 처리할 수 있는 메시지가 존재하지 않으면 새로운 메시지가 도착할 때까지 동기적으로 대기합니다.

Run to Completion

각 메시지의 처리는 다른 메시지의 처리를 시작하기 전에 완전히 끝납니다.

- 나름대로 해석
Call Stack에 들어온 함수 컨텍스트는 해당하는 함수 컨텍스트의 내부가 아닌 다른 외부의 함수 컨텍스트가 생성되기 전에는 끝이 난다. 
그러므로 다른 외부의 함수 컨텍스트가 실행되기 위해 먼저 들어온 함수 컨텍스트가 종료 되어야 한다.
즉, 싱글 스레드 언어이다.

싱글 스레드 언어의 특징
단일 호출 스택이 있으며 한번에 하나의 일만을 처리한다.
함수가 호출(실행)되면 스택에 push 되고 결과값을 반환하면 pop 된다.

 

이 모델의 단점은, 만약 메시지를 처리할 때 너무 오래 걸리면 웹 애플리케이션이 클릭이나 스크롤과 같은 사용자 상호작용을 처리할 수 없다는 점입니다.

- 나름대로 해석
싱글 스레드 언어이므로 Call Stack이 비워져야 클릭이나 스크롤과 같은 이벤트 콜백이 실행될 수 있으며 실행 시간이 오래 걸리는 컨텍스트가 Call Stack에 들어가게 되면 사용자 상호작용을 위한 콜백이 실행되기까지 기다리는 시간이 길어진다.

 

 

이벤트 루프의 예시

setTimeout이 실행되는 과정

(function() {

  console.log('시작');

  setTimeout(function cb() {
    console.log('콜백 1: 콜백 메시지');
  }); // has a default time value of 0

  console.log('평범한 메시지');

  setTimeout(function cb1() {
    console.log('콜백 2: 콜백 메시지');
  }, 0);

  console.log('종료');

})();

// "시작"
// "평범한 메시지"
// "종료"
// "콜백 1: 콜백 메시지"
// "콜백 2: 콜백 메시지"

 

  1. console.log('시작')가 Call Stack에 들어가고 실행되어 빠진다.
  2. setTimeout이 CallStack에 들어와 실행되면 Browser API인 timer를 호출하며 cb() 라는 콜백함수를 Event Table에 등록한다.
  3. console.log('평범한 메시지')가 Call Stack에 들어가고 실행되어 빠진다.
  4. setTimeout이 CallStack에 들어와 실행되면 Browser API인 timer를 호출하며 cb1() 라는 콜백함수를 Event Table에 등록한다.
  5. console.log('종료')가 Call Stack에 들어가고 실행되어 빠진다.
  6. timer가 종료되면 event가 발생하고 Event Table에서 cb() 함수를 찾아  Callback Queue(Message Queue)로 이동시킨다.
  7. 두번째 setTimeout의 timer 또한 종료되어 cb1() 함수를 찾아  Callback Queue(Message Queue)로 이동시킨다.
  8. Call Stack이 비어 있는 경우 Callback Queue에서 cb() 꺼내 Call Stack에 들어가고 실행되어 빠진다.
  9. Call Stack이 비어 있는 경우 Callback Queue에서 cb1() 꺼내 Call Stack에 들어가고 실행되어 빠진다.

즉 두번째 매개변수는 큐에 추가하기까지 기다릴 최소 지연 시간을 나타내며 정확한 시간을 보장하지 않는다.

 

아래 웹사이트에서는 이 과정을 직접 눈으로 확인할 수 있다.

http://latentflip.com/loupe/ 

 

Job Queue

ECMA 2015에서 나온 개념으로 Promise를 사용하는 경우 Job Queue를 사용하게 된다.

JobQueue의 경우 메시지큐보다 우선권을 가지고 있어 메시지큐에 쌓인 함수보다 먼저 실행된다

메시지큐가 놀이기구를 타려고 줄을 서서 기다리는 사람들이라면 JobQueue 우대권을 가지고 놀이기구를 타는 사람들이라고 비유할 있다.

댓글