본문 바로가기

Front-end/JavaScript

[JavaScript] setTimeout과 setInterval을 이용한 호출 스케줄링

일정 시간이 지난 후에 원하는 함수를 예약 실행할 수 잇게 하는 것을 '호출 스케줄링(scheduling a call)'이라고 한다.

호출 스케줄링을 구현하는 방법

  • SetTimeout
  • SetInterval

1. SetTimeout

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

⭕️ 매개변수

  • func | code : 실행하고자 하는 코드로, 함수 또는 문자열형태이다. (대개는 함수가 들어감. 문자열은 추천하지 않는다고 함)
  • delay : 실행 전 대기시간으로, 단위는 밀리초이며 기본값은 0이다.
  • arg1, arg2... : 함수에 전달할 인수
//기본예시
function sayHi() {
  alert('안녕하세요.');
}

setTimeout(sayHi, 1000);
//1초 뒤에 '안녕하세요' 가 얼럿창에 표시

//인수를 넘겨줄 때
function sayHi(who, phrase) {
  alert( who + ' 님, ' + phrase );
}

setTimeout(sayHi, 1000, "째욱", "안녕하세요."); // 째욱님, 안녕하세요.
더보기

**주의사항**

setTimeout에 함수를 넘길 때, 함수 뒤에 ()을 붙이면 안된다!!

setTimeout은 함수의 참조 값을 받도록 정의되어 있는데 sayHi()를 인수로 전달하면 함수 실행 결과가 전달되어 버린다.

그런데 sayHi()엔 반환문이 없기 때문에 호출 결과는 undefined가 되겠지??

따라서 setTimeout은 스케줄링할 대상을 찾지 못해, 원하는 대로 코드가 동작하지 않는다.

// 잘못된 코드
setTimeout(sayHi(), 1000);

 

2. clearTimeout

setTimeout을 호출하면 '타이머 식별자'가 반환된다. 스케줄링을 취소하고싶을 때 이 식별자를 사용하면 된다.

let timerId = setTimeout(...);
clearTimeout(timerId);
//식별자는 timeId

3. setInterval

setInterval 메서드는 setTimeout과 동일한 문법을 사용한다.

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

인수도 동일하다. 다만, setTimeout이 이 함수를 단 한번만 실행하는 것과 달리 setInterval은 함수를 주기적으로 실행하게 한다.

또한 clearInternal(식별자)를 사용하면 함수 호출을 중단할 수 있다.

// 2초 간격으로 메시지를 보여줌
let timerId = setInterval(() => alert('째깍'), 2000);

// 5초 후에 정지
setTimeout(() => { clearInterval(timerId); alert('정지'); }, 5000);

3. 중첩 setTimeout

무언가를 일정 간격을 두고 실행하는 방법에는 크게 2가지가 있다.

  • setInterval을 이용
  • 중첩 setTimeout을 이용
let timerId = setInterval(() => alert('째깍'), 2000);

let timerId = setTimeout(function tick() {
  alert('째깍');
  timerId = setTimeout(tick, 2000); // (*)
}, 2000);

다섯번째 줄의 setTimeout은 (*)표시된 줄의 실행이 끝나면 다음 호출을 스케줄링한다. 그럼 다시 2초뒤에 tick이 실행되고 이 루프가 반복된다.

 

CPU 소모가 많은 작업을 주기적으로 실행하는 경우에도 setTimeout을 재귀 실행하는 방법이 유용하다. 작업에 걸리는 시간에 따라 다음 작업을 유동적으로 계획할 수  있기 때문에.

(위의 예시에서는 tick()함수 안에 있는 timerId의 delay를 유동적으로 조정할 수 있겠죠?)

중첩 setTimeout을 이용하는 방법은 지연 간격을 보장하지만 setInterval은 이를 보장하지 않는다.

// setInterval
let i = 1;
setInterval(function() {
  func(i++);
}, 100);

//setTimeout 중첩
let i = 1;
setTimeout(function run() {
  func(i++);
  setTimeout(run, 100);
}, 100);

첫번째 예시에선, 내부 스케줄러가 func(i++)를 100밀리초마다 실행한다.

그런데 setInterval을 사용하면 func호출 사이의 지연 간격이 실제 명시한 간격(100ms)보다 짧아진다!

이는 func을 실행하는 데 ‘소모되는’ 시간도 지연 간격에 포함시키기 때문입니다. 저 100ms라는 간격 안에서 함수가 실행되는 시간이 좀 더 길어지면 지연시간에서 깎아버리는것. 첫번째 실행한 함수가 시작했을때부터 명시한 간격이 지나면 무조건 실행.

그렇다면 func을 실행하는 데 걸리는 시간이 명시한 지연 간격보다 길 때는?

이런 경우는 엔진이 func의 실행이 종료될 때까지 기다려준다. func의 실행이 종료되면 엔진은 스케줄러를 확인하고, 지연 시간이 지났으면 다음 호출을 바로 시작한다.

따라서 함수 호출에 걸리는 시간이 매번 delay 밀리초보다 길면, 모든 함수가 쉼 없이 계속 연속 호출됩니다.

 

한편, 중첩 setTimeout을 이용하면 다음과 같이 실행 흐름이 이어진다.

중첩 setTimeout을 사용하면 명시한 지연(여기서는 100ms)이 보장된다.

이렇게 지연 간격이 보장되는 이유는 이전 함수의 실행이 종료된 이후에 다음 함수 호출에 대한 계획이 세워지기 때문이다.

 

4. 대기시간이 0인 setTimeout

setTimeout(func, 0)이나 setTimeout(func)을 사용하면 setTimeout의 대기 시간을 0으로 설정할 수 있다.

이렇게 대기 시간을 0으로 설정하면 func을 ‘가능한 한’ 빨리 실행할 수 있다. 다만, 이때 스케줄러는 현재 실행 중인 스크립트의 처리가 종료된 이후에 스케줄링한 함수를 실행한다.이런 특징을 이용하면 현재 스크립트의 실행이 종료된 ‘직후에’ 원하는 함수가 실행될 수 있게 할 수 있다.

//스크립트
setTimeout(() => alert("World"));

alert("Hello");
//스크립트

예시에서 첫 번째 줄은 '0밀리초 후에 함수 호출하기’라는 할 일을 '계획표에 기록’해주는 역할을 한다. 그런데 스케줄러는 현재 스크립트(alert 함수)의 실행이 종료되고 나서야 '계획표에 어떤 할 일이 적혀있는지 확인’하므로, Hello가 먼저, World은 그다음에 출력된다.