setInterval 대신 setTimeout 쓰기

특정 간격마다 이벤트 실행이 필요하면 무지성으로 setInterval을 사용했는데, 특정 상황에서는 이러한 방식이 매우 좋지 않다는 사실을 알게 되었다. setInterval의 경우에는 콜백으로 등록한 작업의 종료와 관계없이 지정한 간격마다 테스크 큐에 콜백을 등록하기 때문에 콜백 함수의 처리가 지정한 간격보다 길어지는 상황에서는 문제가 발생할 수 있다.

  • 작업이 누적되어 메모리 사용량이 높아진다.
  • 작업 누적이 지속되면 브라우저가 동작할 수 없는 상태에 도달할 수 있다.
  • 처리가 된다고 하여도 뒤죽박죽으로 실행될 수 있다.

따라서 명시한 간격내에 작업이 100% 끝난다고 보장할 수 없다면 이를 대체하는 방식으로 주로 사용되는 setTimeout으로 작업을 반복하도록 하는 것이 바람직하다.


setTimeout으로 반복

전통적으로 setTimeout으로 특정 간격마다 반복시키는 방법이다.

const repeat = (task: () => Promise<void>, interval: number) => {
    let timerId = null;

    const run = async () => {
        await task()
        timerId = setTimeout(run, interval)
    }
    timerId = setTimeout(run, interval)

    return () => {
        clearTimeout(timerId)
    }
}

다만 이 방식은 해당 작업이 종료된 이후에 다시 타이머가 실행되므로 실질적으로 정확한 타이밍마다 실행된다고 보기는 어렵다.

repeat(async () => {
    return new Promise<void>((resolve) => {
        setTimeout(() => {
            console.log('Hello, world!');
            resolve();
        }, 500);
    });
}, 1000);

가령 위 코드를 동작시키면 실질적으로 코드는 1.5초마다 실행된다. 그럼에도 안정적인 처리가 우선인 작업이라면 이 방식을 사용하는 것이 바람직하다.


타이밍을 보장하도록 개선

const repeat = (task: () => Promise<void>, interval: number) => {
    let timerId = null;

    const run = async () => {
        const startTime = Date.now();
        await task();
        const executionTime = Date.now() - startTime;
        const nextInterval = Math.max(interval - executionTime, 0);
        timerId = setTimeout(run, nextInterval);
    }
    timerId = setTimeout(run, interval);

    return () => {
        clearTimeout(timerId);
    }
}

위와같이 콜백 함수의 실행 시간을 측정하여 타이머의 간격에서 해당 시간만큼 빼준다.

repeat(async () => {
    return new Promise<void>((resolve) => {
        setTimeout(() => {
            console.log('Hello, world!');
            resolve();
        }, 500);
    });
}, 1000);

개선된 코드로 같은 코드를 동작시키면 해당 코드는 예상대로 1초마다 실행된다. 다만 이 코드는 실행 시간을 뺀 만큼을 간격으로 잡기 때문에 콜백의 정확한 종료 시간을 예측하기 어려운 경우 일관된 간격으로 실행된다고 보장하기 어렵다.

따라서 순간적인 딜레이가 발생할 수 있는 작업이면서 간격을 보장해야 하는 작업에 적합하다. (가령 이 정도는 진짜 setInterval 써도 상관없겠지? 싶은 작업이지만 간격내에 작업이 100% 끝난다고 보장할 수 없으나 가급적 빨리 처리되어야 하는 작업)


함께 고려할 만한 내용들

  • requestAnimationFrame을 활용한 반복 : 브라우저의 프레임 리프레시 주기에 맞춰 반복시 고려 (애니메이션 및 그래픽 작업시 유용)
  • 작업에 따른 최적화 : 거대한 작업인 경우 웹 워커 및 백그라운드 처리 방안 고려
  • 병렬 처리와 비동기 작업 : 비동기 작업인 경우 병렬 및 작업 스케줄링에 대한 고려
  • 타이머의 정확성과 정밀도 : 짧은 간격으로 반복할 경우 브라우저 성능에 대한 고려

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.