들어가기에 앞서
- 이전글 : Event-Loop Part 1 : Big Picture
- 이 글은 setTimeout(), setImmediate(), process.nextTick() 으로 설정한 callback function이 어떤 순서로 실행되는지에 대한 설명입니다.
- 추가적인 그림

이전에 위와 같은 그림을 그렸었는데, 이전글과 이번글을 쓰는데 상당히 많이 참조한 이 글에 나오는 아래 그림을 바탕으로 설명하겠습니다(소스코드도 많이 가져다 썼습니다).

4. 이 글에서 setTimeout()는 setTimeout 함수 자체를 의미하고, setTimeout(cb)는 setTimeout()에서 설정한 callback function을 의미합니다.
심플한 결론
- setTimeout(cb, 0)이 setImmediate(cb)보다 먼저 실행된다
- process.nextTick(cb)은 next tick queue에 들어가며, 각 phase사이사이에서 호출된다
- process.nextTick(cb)은 timer phase(timer queue)이후부터 호출될것 같지만, 실제로는 먼저 호출된다
- 순서는 예상과 다르게 호출 될 수 있다
설명
setTimeout(cb, 0)은 0초후에 실행되지 않는다
const start = process.hrtime();
setTimeout(() => {
const end = process.hrtime(start);
console.log(`timeout callback executed after ${end[0]}s and ${end[1]/Math.pow(10,9)}ms`);
}, 1000);
// output
// timeout callback executed after 1s and 0.001875707ms
setTimeout(cb)은 timer queue에 들어가있을테지만, loop가 딱 1초가 지나는 순간에 timer queue에서 딱 1초가 지났는지 판별해서 cb을 호출하기는 어렵기 때문이다(확률이 매우 낮을것으로 예상). 저 1000은 단지, 최소한 1초안에는 호출되지 않는다는걸 보장할 뿐이다.
setTimeout() vs setImmediate()
//index.js
setTimeout(function(){
console.log("Timeout");
}, 1000);
setImmediate(function(){
console.log("Immedate");
});
// output
// Immediate
// Timeout
- setTimeout()이 실행되고 cb이 timer queue에 들어간다
- setImmediate()가 실행되고 cb이 immediate queue에 들어간다
- 루프를 돌리기 시작하고, timer queue에 있는 cb은 아직 1초가 지나지 않았기 때문에 실행되지 않는다
- 다른 phase를 지나 immediate queue에 들어있는 cb펑션을 호출한다
- 한바퀴 돌았으니까 이제 다시 timer queue를 돌고 이제, 첨에 등록해놨던 setTimeout(cb)이 실행된다
- output이 항상 같다
이번엔 setTimeout(cb)가 0초후에 실행되도록 해보았다
//index.js
setTimeout(function(){
console.log("Timeout");
}, 0);
setImmediate(function(){
console.log("Immediate");
});
// output
// Timeout
// Immediate
- setTimeout()이 실행되고 cb가 timer queue에 들어간다
- setImmediate()가 실행되고 cb가 immediate queue에 들어간다
- 루프를 돌리기 시작하고, timer queue에 있는 cb펑션은 0초가 지났기 때문에 호출된다
- 다른 phase를 지나 immediate queue에 들어있는 cb펑션을 호출한다
- output이 항상 일정하게 출력되지는 않는다. 왜냐하면 0초후에 실행하라고 명령해도 내부적으로는 1ms로 설정된다고 한다. 따라서 시스템상 1ms가 안지났다고 판단해서, 해당 cb이 호출되지 않고 다음 phase로 넘어갈 수 있기 때문이다.
setTimeout() vs setImmediate() vs process.nextTick()
setImmediate(() => console.log('this is set immediate 1'));
setImmediate(() => console.log('this is set immediate 2'));
setImmediate(() => console.log('this is set immediate 3'));
setTimeout(() => console.log('this is set timeout 1'), 0);
setTimeout(() => {
console.log('this is set timeout 2');
process.nextTick(() => console.log('this is process.nextTick added inside setTimeout'));
}, 0);
setTimeout(() => console.log('this is set timeout 3'), 0);
setTimeout(() => console.log('this is set timeout 4'), 0);
setTimeout(() => console.log('this is set timeout 5'), 0);
process.nextTick(() => console.log('this is process.nextTick 1'));
process.nextTick(() => {
process.nextTick(console.log.bind(console, 'this is the inner next tick inside next tick'));
});
process.nextTick(() => console.log('this is process.nextTick 2'));
process.nextTick(() => console.log('this is process.nextTick 3'));
process.nextTick(() => console.log('this is process.nextTick 4'));
// output
// this is process.nextTick 1
// this is process.nextTick 2
// this is process.nextTick 3
// this is process.nextTick 4
// this is the inner next tick inside next tick
// this is set timeout 1
// this is set timeout 2
// this is set timeout 3
// this is set timeout 4
// this is set timeout 5
// this is process.nextTick added inside setTimeout
// this is set immediate 1
// this is set immediate 2
// this is set immediate 3
- setImmediate(cb)가 immediate queue에 들어간다
- setTimeout(cb) 가 timer queue에 들어간다
- process.nextTick(cb) 가 next tick queue에 들어간다
- 루프가 timer phase로 가기전에 next tick queue를 먼저 돈다
- 두번째 process.nextTick(cb)이 호출될때 안에서 한번더 process.nextTick()이 호출되었기 때문에, next tick queue 에 하나가 더 쌓인다. 이거는 네번째 process.nextTick(cb)이 호출되고 나서 호출된다(=> this is the inner next tick inside next tick)
- 이제 next tick queue가 비었으므로, timer queue를 돌기 시작한다
- 모든 setTimeout(cb)이 0초후에 실행되도록 설정되었고, next tick queue를 도는동안 최소 1ms는 지났기 때문에 timer queue에 있는 모든 cb을 호출한다
- 두번째 setTimeout(cb)이 호출될때 process.nextTick()을 호출했기 때문에, next tick queue에 cb이 하나 들어가진다
- timer queue를 다 돌고 i/o event queue 에 들어가기에 앞서 next tick queue를 한번 검사한다. 안에 방금전 두번째 setTimeout()에서 등록한 cb이 있기 때문에 그걸 호출한다( => this is process.nextTick added inside setTimeout)
- i/o event queue에 pending cb도 없고, i/o polling할것도 없기 때문에 immediate queue로 넘어간다. 코드의 맨 처음 설정한 setImmediate(cb)부터 하나씩 호출한다
I/O 첨가
//index.js
setTimeout(function(){
console.log("Timeout");
}, 0);
setImmediate(function(){
console.log("Immediate");
});
// output
// Timeout
// Immediate
- readFile()이 실행되고 다른 일이 없으니까 main thread는 hello.txt가 읽히기를 기다리고(i/o polling) 다 읽혀지면, cb을 호출한다
- timer queue에 하나 담기고, immediate queue에 하나 담긴다
- timer phase -> i/o polling -> immediate phase 순서기 때문에, 언제나 setImmediate(cb)가 먼저 호출된다. 다시말해서 전체적으로는 timer queue에 도 cb이 있고 immediate queue에도 cb이 있지만, i/o polling다음에 살펴볼 queue가 immediate기 때문에 setImmediate(cb)가 호출되는것이다.
참고 자료 리스트
마지막으로
지적과 질문은 환영입니다. 읽어주셔서 감사합니다.
3 replies on “Event-Loop Part 2 : setTimeout() vs setImmediate() vs process.nextTick()”
링크가 .local로 되어 있어서 불편해요… 픽스 부탁드립니다.
그리고 내부적으로는 1ms로 설정된다고 한다 이 부분 링크가 깨져있어요
그리고 I/O 첨가 부분의 코드가 처음 setTimeout 부분 체크할떄의 코드와 동일합니다… 수정 부탁드릴게요