본문 바로가기
JavaScript

setTimeout과 setInterval

by 해-온 2023. 11. 2.

setTimeout과 setInterval을 활용하면 일정 시간이 지난 후 원하는 함수를 실행할 수 있다.

프로젝트에서 이러한 호출 스케쥴링(scheduling a call)을 사용하고 있는데

제대로 이해하지 못하고 사용하고 있는 거 같아 이번 기회에 공부해보려고 한다.

 

setTimeout

setTimeout의 경우 일정 시간이 지난 후 원하는 함수를 실행한다.

원하는 함수를 한 번 실행한 후 종료된다.

 

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

 

첫 번째 매개변수인 func|code는 일정 시간이 지난 후 실행될 함수이다.

두 번째 매개변수인 delay는 실행 전 대기 시간으로 밀리초 단위를 작성하며, 기본값은 0이다.

마지막 매개변수들인 arg1, arg2... 는 실행될 함수에 전달할 인수들이다.

 

function sayHi() {
  alert('안녕하세요.');
}

setTimeout(sayHi, 1000);

 

위와 같이 함수를 정의하고 setTimeout의 첫 번째 매개변수로 넣어 실행시킬 수 있다.

해당 코드를 실행하면 1초 후에 sayHi가 실행된다.

 

function sum(a, b){
    alert(a + b);
}

setTimeout(sum, 1000, 1, 2);

 

이렇게 arg 자리에 인수를 넣으면 함수에 전달된다.

위 예시를 실행하면 a와 b를 더한 3이 출력되는 것을 볼 수 있다.

 

setInterval

setTimeout과 달리 함수를 주기적으로 실행한다.

 

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

 

사용 방법은 setTimeout과 같다.

 

function sum(a, b){
    alert(a + b);
}

setInterval(sum, 1000, 1, 2);

 

아까 setTimeout에 사용했던 예시 코드인데

setTimeout 자리에 setInterval만 넣어주면 주기적으로 작동한다.

 

 

이렇게 1초마다 alert가 뜨는 모습을 볼 수 있다.

 

이때, alert 창을 끄지 않고 있다면 어떻게 될까?

끄고 나서 1초 후 창이 생겨나게 될까?

 

정답은 아니다.

대부분의 브라우저에서는 alert/confirm/prompt 창이 떠있더라도 타이머를 멈추지 않는다.

 

alert를 띄우고 기다렸다가 꺼보니

 

이렇게 1초가 실행되기도 전에 바로 다음 창이 나타나는 것을 볼 수 있다.

 

clearTimeout & clearInterval

이러한 호출 스케쥴링은 clearTimeout과 clearInterval을 통해 취소할 수 있다.

setTimeout이나 setInterval을 호출하면 타이머 식별자가 반환되는데

이 타이머 식별자를 clearTimeout이나 clearInterval에 넣어 사용하면 된다.

 

let timerId = setTimeout(...);
clearTimeout(timerId);

 

 

이 타이머 식별자의 경우 브라우저와 Node.js 일 때에 따라 형태가 다르다.

브라우저 환경의 경우 타이머 식별자는 숫자이고, Node.js의 경우 객체가 반환된다.

 

let timerId = setTimeout(() => console.log('안녕하세요!'), 2000);
console.log(timerId);

 

위 코드를 브라우저와 Node.js 환경일 때 작성해보았는데

이렇게 브라우저의 경우 숫자로

 

Node.js 환경일 때는 객체로 나타나는 것을 직접 확인할 수 있었다.

 

 

+ 가비지 컬렉션과 setInterval·setTimeout

setInterval이나 setTimeout에 함수를 넘기면 함수에 대한 내부 참조가 새롭게 만들어지고, 참조 정보는 스케쥴러에 저장된다.

해당 함수를 참조하지 않아도 setInterval과 setTimeout에 넘긴다면 이 함수는 가비지 컬렉션의 대상이 되지 않는다.

 

특히, setInterval의 경우, claerInterval이 호출되기 전까지 함수에 대한 참조가 메모리에 유지된다.

만약에 setInterval 내부에 있는 함수가 외부 렉시컬 환경을 참조한다고 가정해 보자.

이 함수가 메모리에 남아있는 동안은 외부 변수 또한 메모리에 남아있다.

즉, 더 많은 메모리 공간이 사용되는 것이다.

따라서 스케쥴링할 필요가 없어졌다면 clearInterval을 사용해 취소하는 것이 좋다.

 

중첩 setTimeout

중첩 setTimeout을 사용하면 setInterval처럼 주기적으로 실행할 수 있다.

 

function repeatLog(text, interval) {
  setTimeout(function tick() {
    console.log(text);
    setTimeout(tick, interval);
  }, interval);
}

repeatLog('안녕하세요!', 1000);

 

그렇다면 setInterval을 사용하는 것과 중첩 setTimeout을 사용하는 것의 차이점은 무엇이 있을까?

 

1. 유연한 스케쥴링 가능

중첩 setTimeout의 경우 유연한 스케쥴링이 가능하다.

호출 결과에 따라 다음 호출을 원하는 방식대로 조정할 수 있다.

 

function scheduleTask() {
  console.log("작업을 수행합니다.");

  let randomDelay = Math.random() * 2000 + 1000; // 1초에서 3초 사이의 랜덤한 지연 시간 설정
  setTimeout(scheduleTask, randomDelay);
}

setTimeout(scheduleTask, 1000);

 

위 예시는 작업마다 1초에서 3초 사이의 랜덤한 지연 시간을 설정하여 작업 간격을 동적으로 변경할 수 있다.

 

 

2. 지연 간격 보장

중첩 setTimeout을 사용하면 지연 간격이 보장된다.

반면, setInterval을 사용하면 지연 간격이 보장되지 않는다.

 

예를 들어, 1초에 한 번씩 console을 호출하는 로직이 있다고 가정해 보자.

setInterval의 경우 아래 사진과 같이 실행된다.

함수를 실행시키며 소요되는 시간도 지연 간격에 포함시키기 때문에,

실질적으로 함수가 실행되는 건 1초가 걸리지 않게 된다.

 

그렇다면 함수가 실행되는 시간이 1초가 넘게 되면 어떻게 될까?

이 경우는 엔진이 함수 실행이 종료될 때까지 기다렸다가 지연 시간이 지났으면 바로 다음 호출을 시작한다.

따라서 매번 함수 실행 시간이 지연 간격보다 길다면 함수는 연속 호출된다.

 

반면, 중첩 setTimeout을 사용하게 되면 아래와 같이 실행된다.

setInterval과 다르게 지연 간격이 보장된다.

중첩 setTimeout의 경우 이전 함수의 실행이 종료된 후 호출이 실행되기 때문이다.

따라서 정확한 지연 간격을 보장하고 싶다면 중첩 setTimeout을 사용하면 된다.

 

 

대기 시간이 0인 setTimeout

setTimeout의 경우 지연 간격의 기본값은 0이다.

0일 경우 작업을 '가능한 한' 빨리 실행되도록 할 수 있다.

setTimeout 함수의 지연 간격을 0으로 호출하면, 일단 실행 중인 태스크가 완료돼야 즉시 실행된다.

어떤 상황에서 쓰일 수 있는지 찾아봤더니 아래와 같은 상황에서 사용할 수 있다고 한다.

이벤트 핸들러를 만들다 보면 이벤트 버블링이 끝나
모든 DOM 트리 레벨에서 이벤트가 핸들링될 때까지 특정 액션을 연기시켜야 하는 경우가 생기곤 합니다.
이럴 때 연기시킬 액션 관련 코드를 지연 시간이 0인 setTimeout으로 감싸면 원하는 동작을 구현할 수 있습니다.

 

그렇다면 브라우저에서 실제 대기 시간은 0일까?

브라우저는 HTML5 표준에서 정한 중첩 타이머 실행 간격 관련 제약을 준수하고 있는데,

해당 표준에서 '다섯 번째 중첩 타이머 이후 대기 시간을 최소 4밀리 초 이상으로 강제해야 한다'라고 명시되어 있다.

 

모던 JS 튜토리얼에 있는 예시를 보면

let start = Date.now();
let times = [];

setTimeout(function run() {
  times.push(Date.now() - start); // 이전 호출이 끝난 시점과 현재 호출이 시작된 시점의 시차를 기록

  if (start + 100 < Date.now()) alert(times); // 지연 간격이 100ms를 넘어가면, array를 얼럿창에 띄워줌
  else setTimeout(run); // 지연 간격이 100ms를 넘어가지 않으면 재스케줄링함
});

// 출력창 예시:
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100

 

이렇게 다섯 번째 호출부터 지연 간격이 4밀리 초 이상으로 증가하는 것을 볼 수 있다.

 

지연 없이 중첩 setTimeout을 5회 이상 호출하거나, 지연 없는 setInterval에서 호출이 5회 이상 이루어지면 강제로 발생한다.

setTimeout 뿐 아니라 setInterval에도 이런 현상이 발생하는데

이는 브라우저에만 적용되는 사항이며, 하위 호환성을 위해 유지되고 있다고 한다.

 

 

 

 

참고: https://ko.javascript.info/settimeout-setinterval#ref-522

'JavaScript' 카테고리의 다른 글

배열  (0) 2023.02.26
클래스  (0) 2023.02.24
객체  (0) 2023.02.21
함수  (0) 2023.02.19
제어문  (0) 2023.02.19

댓글