JavaScript

[Javascript] Event Loop

prefer2 2022. 4. 24. 16:21

 

자바스크립트는 단일 스레드(single thread)이면서 비동기 작업을 수행할 수 있다. 이가 가능한 이유를 알아보자

JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks -MDN-

비동기로 동작하는 핵심적인 이유는 자바스크립트가 아니라 브라우저가 가지고 있다. 자바스크립트가 '단일 스레드' 기반의 언어라는 말은 ‘자바스크립트 엔진이 단일 호출 스택을 사용한다’에서 나온 말이다. 실제로 자바스크립트가 구동되는 환경(브라우저, nodejs)에서는 주로 여러개의 스레드가 사용된다. 이들 사이의 연결 작업을 해주는 것이 event loop이다.

 

event loop


Event Loop는 Call Stack과 Callback Queue의 상태를 체크하여, Call Stack이 빈 상태가 되면, Callback Queue의 첫번째 콜백을 Call Stack으로 밀어넣는다. 이벤트 루프는 '현재 실행중인 태스크가 없는지' '태스크 큐에 태스크가 있는지'를 반복적으로 확인한다.

이러한 반복적인 행동을 틱(tick)이라 부른다.

task는 일의 단위. task에는 어떤 것들이 들어가는 걸까?

  • Event
  • Parsing
  • Callback
  • Using a resource(I/O 등)
  • Reacting to DOM 조작
  • script(<script src="...">)

 

  1. 엔진이 특정 태스크를 처리하는 동안엔 렌더링이 절대 일어나지 않는다
  2. 태스크 처리에 긴 시간이 걸리면, 브라우저는 태스크를 처리하는 동안에 발생한 사용자 이벤트 등의 새로운 태스크들을 처리하지 못한다. 예시로 코드에 무한루프가 실행되는 동작이 있다면 화면의 렌더링이 전혀 되지 않는 것을 발견할 수 있다.

이벤트 루프는 frame과도 불과분하게(inextricably) 연결되어 있다. js code를 실행하기도 하지만 새로운 frame을 계산하기도 한다. 최신 기기들은 대부분 60fps(1초에 60 frame)를 지원한다. event loop가 60fps를 지킨다는 말은 한개의 task마다 16.6 ms시간을 가질 수 있다는 말이다.

 

task queue(macrotask queue)


태스크 큐는 콜백 함수들이 대기하고 있는 큐이다. 이벤트 루프는 콜 스택이 빌 때마다 큐에서 task를 가져온다.

  • 모든 비동기 API들은 작업이 완료되면 콜백 함수를 태스크 큐에 추가한다.
  • 이벤트 루프는 '현재 실행중인 태스크가 없을 때'(주로 호출 스택이 비워졌을 때) 태스크 큐의 첫 번째 태스크를 꺼내와 실행한다.

 

microtask queue


모든 프라미스 동작은 마이크로태스크 큐에 들어가서 수행된다. then/catch/finally 핸들러가 큐에 들어간다. 큐에 들어간 핸들러 각각은 현재 코드가 완료되고, 큐에 적체된 이전 핸들러의 실행이 완료되었을 때 실행된다.

  • 마이크로태스크 큐는 먼저 들어온 작업을 먼저 실행(FIFO, first-in-first-out).
  • 실행할 것이 아무것도 남아있지 않을 때만 마이크로태스크 큐에 있는 작업이 실행되기 시작
console.log('script start');

setTimeout(function () {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function () {
    console.log('promise1');
  })
  .then(function () {
    console.log('promise2');
  });

console.log('script end');

// 결과
// script start -> script end -> promise1 -> promise2 -> setTimeout

지금까지 내용을 이해했다면 이 코드의 순서가 이해될 것이다!

위에서 언급했던 문장을 다시 생각해보자. 태스크 처리에 긴 시간이 걸리면, 브라우저는 태스크를 처리하는 동안에 발생한 사용자 이벤트 등의 새로운 태스크들을 처리하지 못한다. microtask queue의 경우 queue가 빌때까지 작업이 계속된다. microtask 전체가 처리되는 동안에는 UI 변화나 네트워크 이벤트 핸들링이 일어나지 않는다. 렌더링이나 네트워크 요청 등의 작업들은 마이크로태스크 전부가 처리되고 난 직후 처리됨을 유의하자.

 

+) animation frame


추가적으로 animation frame까지 더 알아보자

requestAnimationFrame API가 실행되면 콜백이 Animation Frames으로 담긴다.

console.log("script start");

setTimeout(function() {
  console.log("setTimeout");
}, 0);

Promise.resolve().then(function() {
  console.log("promise1");
}).then(function() {
  console.log("promise2");
});

requestAnimationFrame(function() {
    console.log("requestAnimationFrame");
})
console.log("script end");

// 결과
// script start -> script end -> promise1 -> promise2 -> 
// requestAnimationFrame -> setTimeout

 

++) render queue


읽어본 글에 의하면 animation frame이 render queue에 들어 있다고 한다.

위와 같은 순서로 일어난다. rendering과정에 대해서도 설명되어있는데 내용이 너무 많고 어렵다.

 

블로그에 evnet loop를 이해했는지를 알 수 있는 좋은 질문들이 있길래 가져와보았다. 질문에 대한 답은 나와있지 않아서 답은 다 내가 적은 거라 틀릴 수도 있다. 옳지 않은 답이거나 더 좋은 답이 있다면 댓글로 고고

  1. Will console output "1"? Why?
function loop() {
    Promise.resolve().then(loop);
}
setTimeout(() => {console.log(1)}, 0);

loop();

A) No. loop가 실행되고, promise에 의해 loop가 microtask queue에 들어가게 된다. 이후 setTimeout의 콜백인 console.log(1)이 task queue에 들어간다. callstack이 비어있기때문에 microtask queue의 loop가 실행되게 되고, 이 과정에서 microtask queue에 계속해서 loop가 추가된다. microtask queue는 빌때까지 동작하기 때문에 무한 루프가 발생하게 된다. microtask에서 벗어날 수 없기 때문에 task queue에는 접근이 불가능하고 결론적으로 console.log(1)은 동작하지 못한다.

 

  1. There is a site that has a link, which has cursor: pointer on :hover style. Also, the website has a button, which :hover style changes background-color from grey to blue. Now, let add a script to the console:
while (true);

what will happen when we point the mouse over the link? Over the button? Why?

A) 아무런 일도 일어나지 않는다. call stack에 무한루프가 걸리기때문에 render과정까지 가지 못한다.

 

  1. What will console output? Why?
Promise.resolve(1)
    .then((x) => { console.log(x); return x + 1 })
    .then((x) => {console.log(x);})

Promise.resolve(10)
    .then((x) => { console.log(x); return x * 10})
    .then((x) => {console.log(x);})

A) 1 → 10 → 2 → 100

 

  1. How to animate a popup element heigh from 0 to auto?

A) 모르겠음ㅋ

 

참조


https://ko.javascript.info/microtask-queue

https://developer.mozilla.org/ko/docs/Web/API/HTML_DOM_API/Microtask_guide

https://meetup.toast.com/posts/89

https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context

https://xnim.me/blog/javascript-browser-event-loop-layout-paint-composite-call-stack

반응형

'JavaScript' 카테고리의 다른 글

[Javascript] 깊은 복사(deep copy)  (0) 2022.03.27
[Javascript] Event  (0) 2022.03.20
웹팩(webpack) 알러지 치료하기 - 1  (0) 2022.02.27
[javaScript] export와 export default의 차이점  (0) 2021.11.30
[Javascript] Async, Await  (0) 2021.11.01