[JS] Event Loop

Kyunghwan Ko·2022년 11월 27일
0

Nodejs

목록 보기
1/1

JS Engine 구조(V8 기준)

  • 왼쪽에서 볼 수 있듯이 JS Engine은 Memory Heap과 Call Stack으로 이루어져있음

용어 개념 정리

Memory Heap

메모리 할당이 일어나는 곳

Heap

구조화되지 않은 넓은 메모리 영역
-> 객체(변수, 함수 등)들이 담긴다.

Call Stack(호출 스택)

  • 실행될 코드의 한 줄 단위로 할당되어 수행되는 자료구조
    (JS는 인터프리터 언어이기에 한줄단위로 코드를 해석하고 실행한다)

Web APIs(노드에서는 백그라운드로 설명됨)

  • 비동기 처리를 담당함

Callback Queue(Task Queue, Event Queue 등 다양한 형태로 설명됨)

  • 비동기 처리가 끝난 후 실행되어야 할 콜백 함수가 차례로 할당된다.
    (콜백함수가 Queue자료구조에 들어가는 것임)

Event Loop

  • Queue에 할당된 함수를 순서에 맞춰 Call Stack에 할당해준다.

동기적인 코드

만약 아래와 같이 동기적으로 동작하는 코드만 존재한다면
Web API(=백그라운드)가 필요없고, Call Stack만으로도 동작이 가능하다

function first(){
  second();
  console.log("첫 번째");
}
function second(){
  third();
  console.log("두 번째");
}
function third(){
  console.log("세 번째");
}
first();
third();

맨 처음 js파일이 호출되면 해당 파일의 코드들을 갖고 있는 anonymous(=main)이 먼저 호출되고
로직을 수행하는 동안 호출된 함수들이 Call Stack에 다 쌓이고 나서 코드가 실행된다.
pop, push를 거치면서 아래의 출력결과가 나온다

세 번째
두 번째
첫 번째
세 번째

자주 만나는 에러

call stack 용량이 초과되면 아래와 같은 에러가 발생합니다.
브라우저와 엔진마다 call stack의 한계점이 다르다
일반적인 경우 1만개를 가지지만 chrome의 경우 약 12만개를 가진다.

Uncaught RangeError: Maximum call stack size exceeded

비동기적인 코드

코드1

console.log("시작"); // a
setTimeout(function() { // b
  console.log("중간"); // c
}, 3000);
console.log("끝") // d

[call stack]

(1) ananymous가 들어가고 -> (2) a가 push됨 -> (3) a가 pop "시작" 출력
-> (4) b 가 push 됨 -> (5) b는 비동기함수 이므로 b가 pop되어 Web APIs에 append
-> (6) d가 push 됨 -> (7) d가 pop "끝" 출력 -> (8) ananymous pop
-> (10) 익명의 콜백함수안에 있는 c를 call stack에 push
-> (11) c가 pop "중간" 출력 -> (12) 익명의 콜백함수 pop -> (13) 인터프리터 종료

[Web APIs]

(8) b에 있는 익명의 콜백함수를 Callback Queue로 보낸다 -> (9) Event Loop가 call stack이 비어있는지 검사한 후 비어있기 때문에 Callback Queue에 있는 익명의 콜백함수를 Call Stack에 push

시작
끝
중간

b가 3000(3초)이 아니라 0이어도 결과는 동일하다

코드2

console.log("시작");
setTimeout(function(){
  console.log("중간");
}, 0);

Promise.resolve()
	.then(function() {
  		console.log("프로미스");
});
console.log("끝");
  • Promise는 동기적으로 동작하지만 then을 만나는 순간 비동기 처럼 동작한다!
  • Promise는 setTimeout()함수보다 우선순위가 더 높기 때문에 Callback Queue에 들어가서
    setTimeout()보다 먼저 call stack에 push 된다.
시작
끝
프로미스
중간

Callback Queue와 이벤트 루프

Task Queue = Event Queue 이고, Callback Queue 안에 Task Queue가 존재한다.
Promise는 Microtask Queue(=Job Queue)에 담기고 setTimeout()은 Task Queue에 담긴다.

우선순위는 다음과 같다
Microtask Queue > Animation Frames > Task Queue
Event Loop는 항시 동작하고 있는대, Call Stack이 비어있을 경우에
Microtask Queue를 먼저 확인해서 작업을 FIFO 처리한 후 Microtask Queue가 다 비었을 때,
Animation Frames 그리고 Task Queue 순서로 FIFO 처리한다.

코드3

console.log("시작");

setTimeout(function() {
  console.log("중간");
}, 0);
Promise.resolve()
	.then(function(){
  		console.log("프로미스");
});
requestAnimationFrame(function(){ // 실제로 이런함수는 없지만  animation frame을 호출하는 함수를 대신 나타낸 것
  console.log("requestAnimationFrame");
});
console.log("끝");

위 코드의 실행결과는 아래와 같다.

시작
끝
프로미스
requestAnimationFrame
중간

Reference

https://www.youtube.com/watch?v=QFHyPInNhbo

profile
부족한 부분을 인지하는 것부터가 배움의 시작이다.

0개의 댓글