setInterval과 setTimeout

TL;DR: setInterval()은 매 t초마다 task queue에 push, setTimeout()은 이전 task 종료 t초 후 task queue에 push.

setTimeout()은 정확한 실행 주기를 보장할 수 없다.

다음 두 코드는 뭐가 다를까?

// A
setInterval(function run() {
    ...something
}, 100);
// B
setTimeout(function run() {
    ...something
    run();
}, 100);

setInterval()은 지정한 주기마다 함수를 실행한다. 이때 함수가 실행되는 실행시간은 반복 주기에 영향을 미치지 않는다. setTimeout()도 지정한 주기마다 함수를 실행한다. 그러나 실행한 함수가 종료된 이후부터 다시 지정한 시간 간격까지 기다린다.

처음의 두 코드 A, B에서 run()함수가 실행시간이 20ms라면, A의 setInterval()run()함수가 100ms마다 실행되고, B의 setTimeout()run()함수가 종료된 이후부터 100ms가 지난 뒤 다시 run()이 실행되어 setInterval()보다 주기가 길어지게된다.

그렇다면 setInterval은 정확한 주기로 실행될까?

Javascript는 싱글스레드다.

setInterval()이나 setTimeout()과 같은 Timing API의 작동 방식을 살펴보자.

  1. Timing API 함수를 Task queue에 집어넣는다.
  2. queue에서 대기하다가 call stack이 비게되면 task를 push한다.
  3. call stack으로 들어온 task를 실행한다

Javascript 엔진은 call stack이 하나기 때문에  이미 call stack에 task가 있다면 다른 task는 queue에서 대기하게 된다. 그래서 setInterval()이 정확히 t초마다 queue에 task를 push하더라도 대기하는 시간이 있을 수 있다.

이런 이유 때문에 setInterval()도 마찬가지로 정확한 시간에 실행됨을 보장할 수 없다.

아직 저 실행 안 끝났는데요?

setInterval()은 이전 함수의 완료 여부에 상관없이 task queue에 push한다. 만약 설정된 interval이 100ms인데 등록된 task의 실행시간이 500ms가 걸리는 코드라면? 당연히 문제가 발생한다.

function count10B() {
    let count = 0;
    while(count < 1e9) {
        count++;
    }
    console.timeEnd();
    console.time();
} // 300ms

setInterval(count10B, 100); // repeat every 100ms

count10B()함수는 실행에 대략 300ms가 소요되는 작업이다. setInterval()은 100ms마다 함수 count10B()를 task queue에 집어넣으므로 다른 작업이 존재하지 않는다고 하면 300ms 동안 첫번째 count10B()가 실행되면 그 다음에 등록되어 있던 두번째 count10B()가 300ms 동안 실행되고, 이러한 과정이 300ms 마다 반복되어 주기는 결국 100ms가 아닌 300ms가 된다.

결론

task의 실행 간격을 일정하게 맞추고 싶다면 setTimeout()을 재귀적으로 구성하여 사용하고,  task의 push 간격을 일정하게 맞추고 싶다면 setInterval()을 사용하자.