이번 포스팅에서는 자바스크립트 엔진이 어떻게 비동기 작업을 처리하는지 알아보자.
자바스크립트의 엔진
자바스크립트 엔진은 Node.js, Chrome에서 사용되는 Google의 V8 엔진을 사용한다.
엔진은 두 가지로 구성되어 있는데 Call Stack
과 Memory Heap
이다.
- Memory Heap : 메모리 할당이 발생하는 부분
- Call Stack : 함수의 호출을 기록하는 Stack 자료구조
자바스크립트 엔진은 싱글 스레드로 동작한다. 싱글 스레드라는 말은 Call Stack을 1개만 가지고 있다는 것이고, 이 말은 한 번에 하나의 작업만을 처리
할 수 있다는 말이다.
한 번에 하나의 작업만을 처리할 수 있는 자바스크립트에서 실제로 어떻게 여러가지 작업들을 처리하는걸까?
자바스크립트 엔진 밖에서도 자바스크립트 실행에 관여를 하는 요소들이 존재한다. 각각 Web API
Task Queue
Event Loop
이다. 이 요소들이 자바스크립트가 비동기적으로 여러 작업들을 처리할 수 있게한다.
Web API와 Event Loop 그리고 Queue
먼저 Web API는 브라우저에서 제공되는 API이며, AJAX나 setTimeout 등의 비동기 작업을 실행한다.
자바스크립트에서 setTimeout 과 같은 함수를 실행하면, 자바스크립트 엔진은 Web API에 setTimeout 을 요청하고 동시에 setTimeout에 넣어준 Callback 까지 전달한다. Call stack 에서는 Web API 요청 이후 setTimeout 작업이 완료되어 제거된다. 그리고 전달 받은 Callback을 Queue에 넘겨준다.
Queue에 있는 작업들을 Event Loop를 통해 Call Stack으로 옮겨진다. Event Loop가 지속적으로 Call Stack이 비어있는지 확인을 하고, 비어있다면 작업들을 Queue에서 꺼내와 Call Stack에 넣는 역할을 한다.
Queue에는 종류가 있다. 바로 Task Queue
Microtask Queue
Animation frames
이다.
Event Loop는 Microtask Queue → Animation frames → Taske Queue 순으로 우선순위를 두어 작업을 가져간다.
요약하면
- Event Loop는 Call Stack에서 처리할 작업이 없을 경우 우선적으로 Microtask Queue를 확인하고 작업이 있다면 작업을 꺼내서 Call Stack에 전달한다.
- Microtask Queue가 비어있다면 Animation frames를 확인하고 작업이 있다면 작업을 꺼내서 Call Stack에 전달한다.
- 여기서 중요한 점은 Animation frames 작업이 완료되면 브라우저 렌더링 작업이 이루어진다.
- 마지막으로 Task Queue를 확인하고 작업이 있다면 작업을 꺼내서 Call Stack에 전달한다.
처리 과정
아래의 코드를 어떤 순서로 자바스크립트가 처리하는지 확인하면 이해하는데 도움이 될 것이다.
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
위에서 설명한 내용을 잘 이해했다면 결과에 납득이 갈 것이다.
그럼 다시 한 번 어떻게 처리하는지 살펴보겠다.
- script 실행 작업이 스택에 등록된다.
- console.log('script start')가 처리된다.
- setTimeout 작업이 stack에 등록되고, Web API에 setTimeout을 요청하고 Callback 함수를 전달한다. 요청한 후 Call Stack에서 setTimeout이 제거된다.
- Web API는 setTimeout 작업(0초 후)이 완료되면 setTimeout callback 함수를 task queue에 등록한다.
- Promise 작업이 stack에 등록되고, Web API에게 Promise 작업을 요청한다. 이때 Promise.then의 callback 함수를 함께 전달한다. 요청 이후 stack에 있는 Promise 작업은 제거된다.
- Web API는 Promise 작업이 완료되면 Promise.then의 callback 함수를 microtask queue에 등록한다.
- requestAnimation 작업이 stack에 등록되고, Web API에게 requestAnimation을 요청한다. 이때 requestAnimation의 callback 함수를 함께 전달한다. 요청 이후 stack에 있는 requestAnimation 작업은 제거된다.
- Web API는 requestAnimation의 callback 함수를 animation frame에 등록한다.
- console.log(‘script end’)가 처리된다.
- ‘script 실행 작업’이 완료되어 stack에서 제거된다.
- stack이 비워있어서 microtask queue에 등록된 Promise.then 의 callback 함수를 stack에 등록한다.
- 첫번째 Promise.then의 callback 함수가 실행되어 내부의 console.log(‘promise1’)가 처리된다.
- 첫번째 Promise.then 다음에 Promise.then이 있다면 다음 Promise.then의 callback 함수를 microtask queue에 등록한다.
- stack 에서 첫번째 Promise.then의 callback 함수를 제거하고 microtask queue에서 첫번째 Promise.then의 callback 함수를 제거한다.
- 두번째 Promise.then의 callback 함수를 stack에 등록한다.
- 두번째 Promise.then의 callback 함수가 실행되어 내부의 console.log(‘promise2’)가 처리된다.
- stack 에서 두번째 Promise.then의 callback 함수를 제거한다.
- microtask 작업이 완료되면 animation frame에 등록된 callback 함수를 꺼내 실행한다.
- 이후 브라우저는 랜더링 작업을 하여 UI를 업데이트한다.
- stack과 microtask queue가 비워있어서 task queue에 등록된 callback 함수를 꺼내 stack에 등록한다.
- setTimeout의 callback가 실행되어 내부의 console.log(‘setTimeout’)이 처리된다.
- setTimeout의 callback 함수 실행이 완료되면 stack에서 제거된다.
요약
- 비동기 작업으로 등록되는 작업은 task와 microtask. 그리고 animationFrame 작업으로 구분된다.
- microtask -> animationFrame -> task의 우선순위를 갖는다.
- animationFrame이 호출된 이후에는 브라우저 렌더링이 발생한다.
참고자료
AnimationFrames는 별도의 Queue가 아닙니다. Task Queue에 들어갑니다. Task Queue는 Macrotask queue라고도 합니다. (Microtask queue와 반대의 의미)
위 코드에서 requestAnimationFrame의 위치를 맨 위로 올려서 실행해보시면 확인하실 수 있습니다.
인용. https://ko.javascript.info/event-loop