Znaczne ograniczanie łańcuchowych liczników czasu JS począwszy od Chrome 88

Jake Archibald
Jake Archibald

W określonych warunkach Chrome 88 (styczeń 2021 r.) znacznie ogranicza łańcuchowe liczniki czasu JavaScriptu w przypadku ukrytych stron. Zmniejszy to wykorzystanie procesora i zmniejszy wykorzystanie baterii. W pewnych skrajnych przypadkach może to spowodować zmianę zachowania, ale w sytuacjach, gdy inny interfejs API byłby bardziej wydajny i niezawodny, liczniki czasu są często używane.

OK, to było dość żargonu żargonu i niejednoznaczne. Zajmijmy się tą kwestią...

Terminologia

Ukryte strony

Stan ukryta oznacza, że aktywna jest inna karta lub okno zostało zminimalizowane. Przeglądarki mogą jednak uznać stronę za ukrytą, gdy jej zawartość jest całkowicie niewidoczna. Niektóre przeglądarki idą dalej niż inne, ale zawsze możesz użyć interfejsu API widoczności strony, aby śledzić zmiany widoczności strony.

Liczniki JavaScriptu

O licznikach czasu JavaScriptu chodzi mi o setTimeout i setInterval, które umożliwiają zaplanowanie wywołania zwrotnego w przyszłości. Liczniki czasu są przydatne i nie zanikają, ale czasami pozwalają określić, kiedy dane zdarzenie jest bardziej efektywne i dokładne.

Minutniki połączone łańcuchem

Jeśli wywołasz funkcję setTimeout w tym samym zadaniu co wywołanie zwrotne setTimeout, drugie wywołanie będzie „łańcuchowe”. W przypadku setInterval każda iteracja jest częścią łańcucha. Łatwiej będzie to zrozumieć za pomocą kodu:

let chainCount = 0;

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

Oraz:

let chainCount = 0;

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

Jak działa ograniczanie

Ograniczanie odbywa się etapami:

Minimalne ograniczanie

Dzieje się tak w przypadku zaplanowanych minutników, jeśli jest spełniony dowolny z tych warunków:

  • Strona jest widoczna.
  • W ciągu ostatnich 30 sekund strona zawierała dźwięki. Może to być dowolny z interfejsów API do tworzenia dźwięku, ale cicha ścieżka dźwiękowa nie jest uwzględniana.

Licznik nie jest ograniczany, chyba że żądany limit czasu wynosi mniej niż 4 ms, a liczba łańcuchów wynosi 5 lub więcej i w takim przypadku limit czasu jest ustawiony na 4 ms. To nie jest nowość, przeglądarki robią to już od wielu lat.

Ograniczenia

Dzieje się tak w przypadku liczników czasu, które zostały zaplanowane, gdy nie działa minimalne ograniczanie. Spełnia też dowolny z tych warunków:

  • Liczba łańcuchów jest mniejsza niż 5.
  • Strona jest ukryta krócej niż 5 minut.
  • WebRTC jest używany. Konkretnie jest to RTCPeerConnection z wartością „open” RTCDataChannel lub „live” MediaStreamTrack.

Przeglądarka sprawdzi liczniki czasu w tej grupie raz na sekundę. Minutniki o podobnym czasie oczekiwania są sprawdzane tylko raz na sekundę, więc będą się grupować w jednym czasie potrzebnym na uruchomienie kodu przez kartę. To też nie jest nowość – przeglądarki robią to od lat.

Intensywne ograniczanie

OK, oto nowa wersja w Chrome 88. Intensywne ograniczanie dotyczy liczników czasu, które są zaplanowane, gdy nie ma zastosowania żaden z warunków minimalnego ograniczania ani ograniczania. Są też spełnione wszystkie te warunki:

  • Strona jest ukryta od ponad 5 minut.
  • Liczba łańcuchów wynosi 5 lub więcej.
  • Strona jest wyciszona przez co najmniej 30 sekund.
  • WebRTC nie jest używany.

W takim przypadku przeglądarka sprawdza minutniki w tej grupie raz na minutę. Podobnie jak wcześniej oznacza to, że liczniki czasu będą się wyświetlać w kolejnych minutach.

Obejścia

Zwykle istnieje lepsza alternatywa dla minutnika lub minutniki można połączyć z innym, aby lepiej wykorzystać procesory i żywotność baterii.

Ankiety stanowe

Jest to najczęstsze (nieprawidłowe) używanie liczników czasu, które służą do ciągłego sprawdzania lub ankietowania, aby sprawdzić, czy coś się zmieniło. W większości przypadków istnieje odpowiednik push, w którym informacje o zmianie informują Cię w momencie jej wprowadzenia, więc nie musisz co jakiś czas sprawdzać. Sprawdź, czy istnieje zdarzenie o takiej samej skuteczności.

Oto kilka przykładów:

Dostępne są też reguły powiadomień, które pozwalają pokazać powiadomienie o określonej godzinie.

Animacja

Animacja ma charakter wizualny, więc nie powinna wykorzystywać procesora, gdy strona jest ukryta.

Narzędzie requestAnimationFrame jest znacznie lepiej planowane w planowaniu pracy animacji niż liczniki czasu JavaScript. Synchronizuje się z częstotliwością odświeżania urządzenia, dzięki czemu otrzymujesz tylko jedno wywołanie zwrotne na każdą wyświetlaną klatkę i zapewniasz maksymalny czas na utworzenie tej klatki. Poza tym requestAnimationFrame poczeka, aż strona staje się widoczna, więc nie obciąża procesora, gdy jest ukryta.

Jeśli możesz zadeklarować całą animację z wyprzedzeniem, rozważ użycie animacji CSS lub interfejsu API animacji internetowych. Mają one te same zalety co requestAnimationFrame, ale przeglądarka może wykonywać dodatkowe optymalizacje, np. automatyczne komponowanie, i jest zwykle łatwiejsze w użyciu.

Jeśli Twoja animacja ma małą liczbę klatek (np. migający kursor), najlepszym rozwiązaniem jest liczniki czasu. Możesz je jednak połączyć z funkcją requestAnimationFrame, by wykorzystać oba te elementy:

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);
}

Sposób użycia:

const controller = new AbortController();

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

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

Testowanie

Ta zmiana zostanie włączona dla wszystkich użytkowników Chrome w wersji 88 (styczeń 2021 r.). Obecnie jest dostępne dla 50% użytkowników Chrome w wersji beta, deweloperskiej i Canary. Jeśli chcesz przetestować tę funkcję, podczas uruchamiania Chrome w wersji beta, deweloperskiej lub Canary użyj tej flagi wiersza poleceń:

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

Argument grace_period_seconds/10 powoduje silne ograniczanie, które uruchamia się po 10 sekundach od ukrycia strony, a nie po pełnych 5 minutach, co ułatwia dostrzeżenie wpływu ograniczania.

Przyszłość

Ze względu na to, że liczniki czasu są źródłem nadmiernego wykorzystania procesora, będziemy nadal szukać sposobów na ich ograniczanie bez naruszania zawartości stron internetowych oraz interfejsów API, które możemy dodawać i zmieniać zgodnie z zastosowaniami. Osobiście chciałbym wyeliminować potrzebę korzystania z funkcji animationInterval na rzecz wydajnych wywołań zwrotnych animacji o niskiej częstotliwości. Jeśli masz pytania, skontaktuj się ze mną na Twitterze.

Autor zdjęcia: Heather Zabriskie, zdjęcie w nagłówku Unsplash.