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

Jake Archibald
Jake Archibald

Chrome 88 (styczeń 2021 r.) znacznie ograniczy łańcuchowe liczniki czasu JavaScriptu w przypadku ukrytych stron w określonych warunkach. Spowoduje to zmniejszenie wykorzystania procesora, co również ograniczy zużycie baterii. Są pewne przypadki, w których zachowanie się zmieni, ale zegary są często używane w miejscach, w których inny interfejs API byłby wydajniejszy i bardziej niezawodny.

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

Terminologia

Ukryte strony

Ogólnie ukryta oznacza, że aktywna jest inna karta lub okno zostało zminimalizowane, ale przeglądarki mogą uznać, że strona jest ukryta, gdy jej zawartość jest całkowicie niewidoczna. Niektóre przeglądarki są w tej kwestii bardziej zaawansowane niż inne, ale zawsze możesz użyć interfejsu API do wykrywania widoczności strony, aby śledzić, kiedy przeglądarka uzna, że widoczność się zmieniła.

Wyzwalacze JavaScript

Przez timery JavaScript mam na myśli setTimeoutsetInterval, które umożliwiają zaplanowanie połączenia zwrotnego na przyszłość. Czasomierze są przydatne i nie znikną, ale czasami służą do sprawdzania stanu, gdy zdarzenie byłoby bardziej wydajne i dokładniejsze.

Łańcuchowe liczniki czasu

Jeśli wywołasz funkcję setTimeout w tym samym zadaniu jako funkcję zwracającą wywołanie setTimeout, drugie wywołanie jest „połączone”. W przypadku setInterval każda iteracja jest częścią łańcucha. Może być łatwiej zrozumieć to 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

Ograniczenie odbywa się etapami:

Minimalne ograniczanie

Dzieje się tak w przypadku ustawionych zegarów, gdy spełniony jest dowolny z tych warunków:

  • Strona jest widoczna.
  • Strona wygenerowała dźwięk w ciągu ostatnich 30 sekund. Możesz użyć dowolnego interfejsu API do generowania dźwięku, ale nieużyteczne są ścieżki audio bez dźwięku.

Timer nie jest ograniczany, chyba że żądany czas oczekiwania jest krótszy niż 4 ms, a liczba elementów łańcucha wynosi 5 lub więcej, w którym to przypadku czas oczekiwania jest ustawiony na 4 ms. To nie jest nowość – przeglądarki stosują tę metodę od wielu lat.

Ograniczenia

Dzieje się tak w przypadku minutników zaplanowanych, gdy minimalne ograniczenie nie ma zastosowania i spełniony jest co najmniej jeden z tych warunków:

  • Liczba łańcuchów jest mniejsza niż 5.
  • Strona była ukryta przez mniej niż 5 minut.
  • Usługa WebRTC jest używana. Chodzi o RTCPeerConnection z wartością „otwarte”RTCDataChannel lub „na żywo”MediaStreamTrack.

Przeglądarka będzie sprawdzać zegary w tej grupie raz na sekundę. Są one sprawdzane tylko raz na sekundę, więc zegary z podobnym czasem trwania będą działać w grupach, co pozwoli zredukować czas, jaki karta potrzebuje na wykonanie kodu. To też nie jest nowością. przeglądarki od lat w pewnym stopniu tak właśnie działają.

Intensywne ograniczanie

Oto nowość w Chrome 88. Intensywne ograniczanie dotyczy zegarów, które są zaplanowane, gdy nie ma zastosowania żadne z warunków minimalnego ograniczania ani ograniczania, a wszystkie te warunki są spełnione:

  • Strona jest ukryta od ponad 5 minut.
  • Liczba ogniw wynosi co najmniej 5.
  • strona nie emitowała dźwięku przez co najmniej 30 sekund;
  • WebRTC nie jest używany.

W tym przypadku przeglądarka będzie sprawdzać zegary w tej grupie raz na minutę. Podobnie jak wcześniej oznacza to, że zegary będą sprawdzane zbiorczo w ramach minutowych kontroli.

Obejścia

Zazwyczaj istnieje lepsza alternatywa dla timera, a czasami można go połączyć z czymś innym, aby oszczędzać procesor i baterię.

Ankietowanie stanu

Jest to najczęstsze (nie)używanie timerów, które służą do ciągłego sprawdzania lub przepytywania, czy coś się zmieniło. W większości przypadków istnieje odpowiednik push, który informuje o zmianach w chwili ich wystąpienia, dzięki czemu nie musisz ich stale sprawdzać. Sprawdź, czy jest jakieś zdarzenie, które osiąga ten sam cel.

Oto kilka przykładów:

Jeśli chcesz wyświetlić powiadomienie w określonym czasie, możesz też użyć wyzwalaczy powiadomień.

Animacja

Animacja jest elementem wizualnym, więc nie powinna zużywać czasu procesora, gdy strona jest ukryta.

requestAnimationFrame znacznie lepiej planuje animacje niż liczniki czasu w JavaScript. Synchronizuje się z częstotliwością odświeżania urządzenia, dzięki czemu otrzymujesz tylko 1 wywołanie zwrotne na wyświetlaną klatkę i masz maksymalną ilość czasu na jej stworzenie. Ponadto requestAnimationFrame będzie czekać, aż strona będzie widoczna, więc nie będzie używać procesora, gdy strona jest ukryta.

Jeśli możesz zdeklarować 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 przeprowadzać dodatkowe optymalizacje, takie jak automatyczne łączenie. Są też zazwyczaj łatwiejsze w użyciu.

Jeśli animacja ma niską liczbę klatek na sekundę (np. migający kursor), obecnie nadal najlepszym rozwiązaniem są liczniki, ale możesz je połączyć z requestAnimationFrame, aby uzyskać najlepsze efekty:

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 włączona dla 50% użytkowników Chrome w wersjach beta, deweloperskiej i Canary. Jeśli chcesz przetestować tę funkcję, podczas uruchamiania Chrome (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, że intensywne ograniczanie mocy obliczeniowej jest uruchamiane po 10 sekundach od ukrycia strony, a nie po 5 minutach, co ułatwia obserwowanie wpływu ograniczania mocy obliczeniowej.

Przyszłość

Ponieważ minutniki powodują nadmierne obciążenie procesora, będziemy nadal szukać sposobów na ich ograniczenie bez zakłócania treści w internecie oraz interfejsów API, które możemy dodać lub zmienić, aby spełniały wymagania dotyczące zastosowań. Osobiście chciałbym wyeliminować konieczność korzystania z funkcji animationInterval na rzecz wydajnych wywołań animacji o niskiej częstotliwości. Jeśli masz pytania, skontaktuj się ze mną na Twitterze.

Zdjęcie w nagłówku: Heather Zabriskie, Unsplash.