티스토리 뷰

실행 시점을 조절할 필요가 있을 때 우리는 위 함수들을 사용합니다.

이 포스팅에 들어오셨다면 아마 이들을 함께 사용하시다가 실행 시점에 의문이 생겨서 오셨을 것 같습니다.


기본 개념

더보기

1. setTimeout()

setTimeout() schedules a script to be run after a minimum threshold in ms has elapsed.

최소 입력한 시간만큼 지난 후 함수가 실행되도록 합니다.

// Execute
setTimeout(() => {
  console.log('setTimeout');
}, 0);

// Output
setTimeout

 

2. setImmediate()

setImmediate() is designed to execute a script once the current poll phase completes.

현재 poll 단계가 완료된 후에 실행됩니다.

// Execute
setImmediate(() => {
  console.log('setImmediate');
});

// Output
setImmediate

 

3. process.nextTick()

nextTickQueue will be processed after the current operation is completed, regardless of the current phase of the event loop.

이벤트 루프와 관계 없이, 현재 작업이 완료된 후 실행됩니다.

// Execute
process.nextTick(() => {
  console.log('nextTick');
});

// Output
nextTick

 

그럼 문제가 되는 예제를 보겠습니다.

※ 실행마다 다른 결과가 나오는 예

// Execute
setTimeout(() => {
  console.log('timeout');
}, 0);
setImmediate(() => {
  console.log('immediate');
});

// Output 1
timeout
immediate

// Output 2
immediate
timeout
※ 위 예제와 비슷한데, 결과가 고정되는 예

// Execute
const fs = require('fs');
fs.readFile('a.js', (result) => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

// Output
immediate
timeout
※ 예측하기 힘든 예

// 한 눈에 보기 위해 들여쓰기를 위와 다른 형식으로 작성했습니다.

// 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

오늘은 이 실행 시점에 대한 의문을 풀어봅시다.
이 의문을 풀기 위해서는 2가지가 필요합니다.

 

1. setTimeout과 setImmediate와 관련이 있는 Event loop에 관한 이해
2. process.nextTick과 관련이 있는 nextTickQueue에 대한 이해

 

위 두 가지를 먼저 살펴보고, 예제 분석을 통해 실제 동작 방식을 알아보겠습니다.


Event loop

Node.js Main thread로, 내부의 각 Phase를 돌면서 애플리케이션을 실행 합니다.
아래 그림과 같이 각 Phase는 Queue로 이루어져있습니다.
Queue에 우리가 등록한 Callback들이 알맞게 담겨서 자신의 실행을 기다리게 되는데요.
각 Phase는 자신 Queue의 모든 Job을 수행하거나, 제한 갯수까지 실행한 후에 다음 Phase로 이동합니다.

Event loop phases

오늘 우리는 이 중에서 timers, poll, check 단계만 필요합니다.

 

Phase 대상 처리 작업
timer setTimeout(func, delay)
setInterval(func, delay)
delay가 지났으면, 등록된 Callback 실행
poll I/O 대부분의 Callback 실행
check setImmediate(func) 등록된 Callback 실행

nextTickQueue

process.nextTick() is not technically part of the event loop. Instead, the nextTickQueue will be processed after the current operation is completed, regardless of the current phase of the event loop.

공식 문서에 따르면 process.nextTick()은 Event loop의 일부가 아니며, nextTickQueue는 현재 진행 중인 작업(C/C++에서의 전환, 실행되어야 하는 자바스크립트 처리)이 끝나면 실행된다고 나와있습니다.

 

Event loop의 Phase와 nextTickQueue가 아까 위의 예제 코드들을 실행한다는 개념을 알았으니, 이제 코드를 하나씩 보면서 실행되는 과정을 알아봅시다.


예제 분석

예제 1

더보기
// Execute
setTimeout(() => {
  console.log('timeout');
}, 0);
setImmediate(() => {
  console.log('immediate');
});

// Output 1
timeout
immediate

// Output 2
immediate
timeout

위의 코드를 Event loop로 나타내면 다음과 같습니다.

1. setTimeout()의 Callback이 timers에 등록.
2. setImmediate()의 Callback이 check에 등록.
3. 이후 Event loop에 따라 실행됩니다.

기대 상황
    1. timers phase에서 등록된 시간이 지났는지 확인.
    2. 0ms 지났으므로 timeout 을 콘솔에 입력.
    3. check phase에서 immediate 를 콘솔에 입력.

실제 결과
    - timers phase와 check phase에 등록된 Callback이 매 번 섞여서 실행됩니다.

 

앞의 Event loop 설명에 따르면, 원래는 기대 상황처럼 작동하는게 맞습니다.
다만 여기서는 하나 함정이 있습니다. 

 

setTimeout(func, delay) 에서 delay에 0을 넣은 경우 0ms 후에 동작하지 않습니다.
Node.js 소스의 lib/timer.js 에는 setTimeout()이 있습니다.

function setTimeout(callback, after, arg1, arg2, arg3) {

  //...skip

  const timeout = new Timeout(callback, after, args, false, true);
  insert(timeout, timeout._idleTimeout);

  return timeout;
}

위에서 쓰인 new Timeout() 은 lib/internal/timer.js에서 찾을 수 있었습니다.

function Timeout(callback, after, args, isRepeat, isRefed) {
  after *= 1; // Coalesce to number or NaN
  if (!(after >= 1 && after <= TIMEOUT_MAX)) {
    if (after > TIMEOUT_MAX) {
      process.emitWarning(`${after} does not fit into` +
                          ' a 32-bit signed integer.' +
                          '\nTimeout duration was set to 1.',
                          'TimeoutOverflowWarning');
    }
    after = 1; // Schedule on next tick, follows browser behavior
  }
  //...skip
}

setTimeout(func, 0)의 0ms는 결국 1ms로 동작합니다.

 

위의 결론에 따라 위의 코드를 다시 살펴보면 실제 결과를 이해할 수 있습니다.

 

A. timers phase에 도달했는데, 1ms 전에 timer phase를 통과 한 경우 check phase의 setImmediate() 가 먼저 실행
B. timers phase에 도달했는데, 1ms가 이미 되었다면 setTimeout() 이 먼저 실행

 

예제 2

더보기
// Execute
const fs = require('fs');
fs.readFile('a.js', (result) => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
     console.log('immediate');
  });
});

// Output
immediate
timeout

위의 코드는 예제 1과 비슷한데 결과가 고정입니다. Event loop로 나타내봅시다.

1. fs.readFile() 이 실행되고, Callback이 poll에 들어갑니다.

2. poll phase에 진입하고, Callback이 실행되므로 setTimeout() 의 Callback이 timers phase에 들어갑니다.
3. setImmediate() 의 Callback이 check phase에 들어갑니다.
4. poll phase를 모두 소진했으니, 다음 phase인 check phase로 이동할 것입니다.

 

같은 I/O 주기 내에서는 Immediate가 먼저 실행된다는 것을 알게 되었습니다.

 

공식 문서 역시 이에 관련된 설명이 있습니다.

The main advantage to using setImmediate() over setTimeout() is setImmediate() will always be executed before any timers if scheduled within an I/O cycle, independently of how many timers are present.

 

예제 3

더보기
// 한 눈에 보기 위해 들여쓰기를 위와 다른 형식으로 작성했습니다.

// 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

뭔가 복잡해보이지만 하나씩 따라가봅시다. 이번에는 nextTickQueue를 사용합니다.

1. setTimeout() 의 Callback이 timers phase에 담깁니다. 1번 예제와 같이 0ms는 1ms가 됩니다.
2. setImmediate() 의 Callback이 check phase에 담깁니다.
3. process.nextTick() 의 Callback이 nextTickQueue에 담깁니다. 그리고 위의 nextTickQueue 설명에 따라 제일 먼저 실행됩니다.

4. nextTickQueue의 Callback인 setTimeout(), setImmediate()의 Callback이 각각 phase에 담기게 됩니다.
5. 이후 이들이 실행되고, 그에 따라 위의 예제의 Output에 해당하는 결과가 나오게 되었습니다.

 


마무리

이렇게 이번 포스팅에서는 예제들을 통하여 setTimeout(), setImmediate(), process.nextTick()이 혼재된 상황에서 어떻게 작동하는지, 특이한 사항은 무엇이 있는지 알아보았습니다.

 

부족한 부분이나 틀린 부분, 혹은 보충 설명이 필요하시다면 피드백 부탁드립니다!

확인 후 반영하겠습니다.

'Node.js' 카테고리의 다른 글

PM2의 Cluster mode는 어떻게 동작할까요?  (2) 2020.01.12
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함