Chrome 88부터 체인으로 연결된 JS 타이머의 과도한 제한

Jake Archibald
Jake Archibald

Chrome 88 (2021년 1월)에서는 특정 조건에서 숨겨진 페이지의 연쇄 자바스크립트 타이머가 과도하게 제한됩니다. 이렇게 하면 CPU 사용량이 줄어 배터리 사용량도 줄어듭니다. 이로 인해 동작이 변경되는 극단적인 사례가 있지만 타이머는 다른 API가 더 효율적이고 신뢰할 수 있는 경우에 사용되는 경우가 많습니다.

자, 꽤 전문 용어로 되어 있고 좀 모호했습니다. 자세히 살펴볼까요?

용어

숨겨진 페이지

일반적으로 숨김은 다른 탭이 활성 상태이거나 창이 최소화되었음을 의미하지만 브라우저에서는 콘텐츠가 전혀 표시되지 않을 때마다 페이지가 숨겨진 것으로 간주할 수 있습니다. 일부 브라우저는 다른 브라우저보다 더 앞서 있지만 언제든지 페이지 공개 상태 API를 사용하여 브라우저에서 공개 상태가 변경되었다고 생각하는 시점을 추적할 수 있습니다.

JavaScript 타이머

JavaScript 타이머setTimeoutsetInterval를 의미하며, 이를 통해 향후 언젠가 콜백을 예약할 수 있습니다. 타이머는 유용하고 사라지지 않지만 이벤트가 더 효율적이고 정확한 경우 상태를 폴링하는 데 사용되는 경우도 있습니다.

체인으로 연결된 타이머

setTimeout 콜백과 동일한 작업에서 setTimeout를 호출하면 두 번째 호출이 '체이닝'됩니다. setInterval를 사용하면 각 반복이 체인의 일부가 됩니다. 코드를 사용하면 더 쉽게 이해할 수 있습니다.

let chainCount = 0;

setInterval(() => {
  chainCount++;
  console.log(`This is number ${chainCount} in the chain`);
}, 500);

추가 조건:

let chainCount = 0;

function setTimeoutChain() {
  setTimeout(() => {
    chainCount++;
    console.log(`This is number ${chainCount} in the chain`);
    setTimeoutChain();
  }, 500);
}

제한 작동 방식

제한은 다음과 같이 단계적으로 발생합니다.

최소 제한

예약된 타이머는 다음 중 하나라도 해당될 때 발생합니다.

  • 페이지가 표시됩니다.
  • 지난 30초 동안 페이지에 소음이 있었습니다. 이는 사운드 제작 API에서 가져올 수 있지만 무음 오디오 트랙은 포함되지 않습니다.

요청된 제한 시간이 4ms 미만이고 체인 수가 5 이상인 경우(이 경우 제한 시간이 4ms로 설정) 타이머는 제한되지 않습니다. 이는 새로운 방식이 아닙니다. 브라우저는 오랫동안 이 작업을 수행해 왔습니다.

사용 제한

이는 최소 제한이 적용되지 않을 때 예약된 타이머에서 발생하며 다음 중 하나라도 참입니다.

  • 체인 수가 5개 미만입니다.
  • 페이지가 5분 미만 동안 숨겨져 있습니다.
  • WebRTC가 사용 중입니다. 구체적으로는 '열림' RTCDataChannel 또는 '실시간' MediaStreamTrack이 있는 RTCPeerConnection가 있습니다.

브라우저가 에 한 번씩 이 그룹의 타이머를 확인합니다. 초당 한 번만 확인되므로 시간 제한이 비슷한 타이머가 함께 일괄 처리되어 탭에서 코드를 실행하는 데 필요한 시간이 통합됩니다. 이는 새로운 방식이 아닙니다. 브라우저는 수년 동안 어느 정도 이 작업을 수행해 왔습니다.

강도 높은 제한

알겠습니다. Chrome 88의 새로운 기능입니다. 강도 제한은 최소 제한 또는 제한 조건 중 어느 것도 적용되지 않고 다음 조건이 모두 참일 때 예약된 타이머에서 발생합니다.

  • 페이지가 5분 이상 숨겨져 있습니다.
  • 체인 수가 5 이상입니다.
  • 페이지가 최소 30초 동안 무음 상태입니다.
  • WebRTC가 사용 중이 아닙니다.

이 경우 브라우저는 당 한 번 그룹의 타이머를 확인합니다. 이전과 마찬가지로 이는 타이머가 이러한 분 단위 확인으로 일괄 처리됨을 의미합니다.

해결 방법

일반적으로 타이머 대신 더 나은 대안이 있습니다. 또는 타이머를 다른 것과 결합하여 CPU 및 배터리 수명을 향상시킬 수 있습니다.

주 투표

이는 타이머의 가장 일반적인 오용이며, 타이머가 계속 변경되었는지 확인하거나 폴링하는 데 사용됩니다. 대부분의 경우 push와 동등한 동작이 있는데, 이 경우 변경사항이 발생할 때 이를 통해 변경사항을 알려주므로 계속 확인할 필요가 없습니다. 동일한 결과를 얻는 이벤트가 있는지 살펴봅니다.

예를 들면 다음과 같습니다.

또한 특정 시간에 알림을 표시하고자 하는 경우 알림 트리거를 사용할 수 있습니다.

애니메이션

애니메이션은 시각적 요소이므로 페이지가 숨겨져 있을 때 CPU 시간을 사용해서는 안 됩니다.

requestAnimationFrame는 JavaScript 타이머보다 애니메이션 작업을 예약하는 데 훨씬 효과적입니다. 기기의 새로고침 빈도와 동기화되어 표시 가능한 프레임당 하나의 콜백만 가져오고 프레임을 구성하는 데 필요한 최대 시간을 확보할 수 있습니다. 또한 requestAnimationFrame는 페이지가 표시될 때까지 기다리므로 페이지가 숨겨질 때 CPU를 사용하지 않습니다.

전체 애니메이션을 미리 선언할 수 있는 경우 CSS 애니메이션 또는 웹 애니메이션 API를 사용해 보세요. 이는 requestAnimationFrame와 동일한 이점이 있지만 브라우저에서 자동 합성과 같은 추가 최적화를 실행할 수 있으며 일반적으로 더 쉽게 사용할 수 있습니다.

애니메이션의 프레임 속도가 낮은 경우 (예: 깜박이는 커서) 타이머는 여전히 가장 좋은 옵션이지만 requestAnimationFrame와 결합하여 두 가지를 모두 활용할 수 있습니다.

function animationInterval(ms, signal, callback) {
  const start = document.timeline.currentTime;

  function frame(time) {
    if (signal.aborted) return;
    callback(time);
    scheduleFrame(time);
  }

  function scheduleFrame(time) {
    const elapsed = time - start;
    const roundedElapsed = Math.round(elapsed / ms) * ms;
    const targetNext = start + roundedElapsed + ms;
    const delay = targetNext - performance.now();
    setTimeout(() => requestAnimationFrame(frame), delay);
  }

  scheduleFrame(start);
}

사용:

const controller = new AbortController();

// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
  console.log('tick!', time);
});

// And stop it:
controller.abort();

테스트

이 변경사항은 Chrome 88 (2021년 1월)의 모든 Chrome 사용자에게 적용됩니다. 현재 Chrome 베타, 개발자, Canary 사용자의 50% 가 사용할 수 있습니다. 테스트하려면 Chrome 베타, 개발자 또는 Canary를 시작할 때 다음 명령줄 플래그를 사용합니다.

--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"

grace_period_seconds/10 인수를 사용하면 5분이 아니라 페이지가 숨겨지고 10초 후에 강렬한 제한이 실행되므로 제한의 영향을 더 쉽게 확인할 수 있습니다.

앞으로

타이머는 과도한 CPU 사용의 원인이므로 웹 콘텐츠를 중단하지 않고 타이머를 제한할 수 있는 방법과 사용 사례에 맞게 추가/변경할 수 있는 API를 계속 살펴볼 예정입니다. 개인적으로 효율적인 저주파 애니메이션 콜백을 위해 animationInterval가 필요하지 않게 하고 싶습니다. 궁금한 점이 있으면 트위터를 통해 저에게 문의해 주세요.

헤더 사진: 헤더 자브리스키, Unsplash