본문 바로가기

프론트엔드

Web Worker로 setTimeout을 백그라운드에서 유지하기

  • 프로젝트: tracking map(유동인구분석 지도)
  • 키워드: web worker, setTimeout, inactive tabs
  • 상황
    1. setTimeout으로 특정 delay 시간 이후 동작해야하는 코드가 있다.
    2. 탭을 유지할 때는 버그가 일어나지 않지만, 다른 탭으로 이동(현재 탭은 비활성화)했다가 돌아오면 원하는 타이밍보다 훨씬 늦게 setTimeout이 실행되는 버그가 있다.
  • 해결방법
    1. setTimout이 의도와 다르게 동작하는 것은 브라우저의 정책 때문이다.
      https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#reasons_for_delays_longer_than_specified
      부하를 줄이기 위해서 탭이 inactive 될 때 delay 시간을 강제로 늘린다.

      Firefox Desktop and Chrome both have a minimum timeout of 1 second for inactive tabs.

    2. 이 문제를 해결하기 위해 'Web Worker'를 사용해보기로 한다. web worker는 독립된 thread에서 스크립트를 실행하기 때문에, inactive tab에서도 setTimeout이 원하는 타이밍에 동작한다.

    3. 아래는 setTimeout을 이용한 기존 로직이다. delay 시간 후 함수를 실행하고자 한다.
        // example.tsx
      useEffect(() => {
        const listener = setTimeout(() => {
          ...함수 실행...
        }, delay);
        
        return () => {
          clearTimeout(listener);
        };
      , [...]);


    4. setTimeout을 대신 실행해 줄 worker script를 작성한다.
       // workers/setTimeout.js
      onmessage = function (e) {
        let delay = e.data;
      
        let timer = setTimeout(() => {
          postMessage(null);
          clearTimeout(timer);
        }, delay);
      };
      delay를 parameter(e.data)로 받고, delay 시간 후 다시 'main script'(worker를 호출한 스크립트)에게 Message를 보내서(postMessage) setTimeout이 동작했음을 알린다.

    5. 이제 이 워커를 main script에 적용한다. 아래는 웹워커를 적용한 코드이다.
        // example.tsx
      useEffect(() => {
        const myWorker = new Worker('workers/setTimeout.js');
       
        myWorker.postMessage(delay);
        myWorker.onmessage = function () {
          ...함수 실행...
        };
        
        return () => {
          myWorker.terminate();
        };
      }, []);


    6. 이제 탭을 이동한 후 돌아와도 setTimeout이 정상적으로 작동해있다!

  • known issues
    1. setTimeout 특성상 1ms 단위까지 원하는 타이밍에 동작하지는 않겠지만, 서비스에서 기대하는 수준의 동작을 구현했다.
    2. worker는 setTimeout만 실행시키고 메인 로직(함수)은 main script에서 실행하는 이유는 해당 함수가 DOM 조작과 관련있기 때문이다.