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의 작동 방식을 살펴보자.
- Timing API 함수를 Task queue에 집어넣는다.
- queue에서 대기하다가 call stack이 비게되면 task를 push한다.
- 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()
을 사용하자.