...
Event loop
이벤트 루프는 Node.js의 Main thread로, 내부의 각 Phase를 돌면서 애플리케이션을 실행 합니다.
아래 그림과 같이 각 Phase는 Queue로 이루어져있습니다.
Queue에 우리가 등록한 Callback들이 알맞게 담겨서 자신의 실행을 기다리게 되는데요.
각 Phase는 자신 Queue의 모든 Job을 수행하거나, 제한 갯수까지 실행한 후에 다음 Phase로 이동합니다.
이 중에서 우리는 timers, poll, check 단계만 필요합니다.
Phase | 대상 | 처리 작업 |
timer | setTimeout(func, delay) setInterval(func, delay) |
delay가 지났으면, 등록된 Callback 실행 |
poll | I/O | 대부분의 Callback 실행 |
check | setImmediate(func) | 등록된 Callback 실행 |
setImmediate( )와 setTimeout( )
setImmediate( )와 setTimeout( )은 비슷하지만, 언제 호출되느냐에 따라 다르게 행동합니다.
타이머가 실행되는 순서는 호출되는 콘텍스트 시점에 따라 다릅니다.
만약 둘 다 메인 모듈에서 호출되면, 프로세스의 성능에 따라 호출되는 시점이 다릅니다. (이것은 머신의 다른 애플리케이션에 의해 영향을 받을 수 있습니다. )
예를 들어, 아래 코드처럼 I/O 사이클에 있지 않은 스크립트를 실행시킬 때(메인 모듈에 있는 경우), 두 개의 타이머가 실행되는 순서는 확실하지 않고, 프로세스의 퍼포먼스에 의해 달라집니다.
// Execute
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
// Output 1
timeout
immediate
// Output 2
immediate
timeout
- setTimeout()의 Callback이 timers에 등록.
- setImmediate()의 Callback이 check에 등록.
- A. timers phase에 도달했는데, 1ms 전에 timer phase를 통과 한 경우 check phase의 setImmediate() 가 먼저 실행
B. timers phase에 도달했는데, 1ms가 이미 되었다면 setTimeout() 이 먼저 실행
setTimeout(func, 0)의 0ms는 결국 1ms로 동작합니다.
그런데 두 개의 호출을 I/O 사이클에 넣으면, immediate 콜백이 항상 먼저 실행됩니다.
setTimeout( ) 보다 setImmediate( )를 사용하는 것의 장점은 I/O 사이클에 스케줄 됐다면 얼마나 많은 타이머가 있는 것에 상관없이 setTimeout( )이 언제나 먼저 실행된다는 것입니다.
// Execute
const fs = require('fs');
fs.readFile('a.js', (result) => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
});
// Output
immediate
timeout
- fs.readFile() 이 실행되고, Callback이 poll에 들어갑니다.
- poll phase에 진입하고, Callback이 실행되므로 setTimeout() 의 Callback이 timers phase에 들어갑니다.
- setImmediate() 의 Callback이 check phase에 들어갑니다.
- poll phase를 모두 소진했으니, 다음 phase인 check phase로 이동할 것입니다.
같은 I/O 주기 내에서는 Immediate가 먼저 실행됩니다.
process.nextTick( )
공식 문서에 따르면 process.nextTick()은 Event loop의 일부가 아니며, nextTickQueue는 현재 진행 중인 작업이 끝나면 실행된다고 나와있습니다.
- process.nextTick(cb)은 next tick queue에 들어가며, 각 phase사이사이에서 호출됩니다
- process.nextTick(cb)은 timer phase(timer queue)이후부터 호출될것 같지만, 실제로는 먼저 호출됩니다
// Execute
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
process.nextTick(() => {
setTimeout(() => console.log('timeout2'), 0);
setImmediate(() => {
process.nextTick(() => console.log('next tick2'));
console.log('immediate2');
});
console.log('next tick');
});
// Output
next tick
timeout
timeout2
immediate
immediate2
next tick2
- setTimeout() 의 Callback이 timers phase에 담깁니다. 1번 예제와 같이 0ms는 1ms가 됩니다.
- setImmediate() 의 Callback이 check phase에 담깁니다.
- process.nextTick() 의 Callback이 nextTickQueue에 담깁니다. 그리고 위의 nextTickQueue 설명에 따라 제일 먼저 실행됩니다.
- nextTickQueue의 Callback인 setTimeout(), setImmediate()의 Callback이 각각 phase에 담기게 됩니다.
- 이후 이들이 실행되고, 그에 따라 위의 예제의 Output에 해당하는 결과가 나오게 되었습니다.
Promise의 큐와 nextTick큐 중에 어느게 먼저 실행될까요?
간단하게 생각하면 됩니다. nextTick 큐가 어느상황이든 가장 먼저 실행된다!
이벤트 비동기 실행순서 : nextTick -> Promise -> setTimeout
# 참고자료
https://short-term.tistory.com/8
https://medium.com/@rpf5573/nodejs-event-loop-part-2-settimeout-vs-setimmediate-vs-process-nexttick-70ba2a9f0895
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.