Przedstawiamy test źródła Scheduler.yield

Tworzenie stron szybko reagujących na opinie użytkowników należy do najtrudniejszych aspektów związanych z wydajnością sieci. Zespół Chrome ciężko pracuje, aby ułatwić tworzenie stron internetowych. Tylko w tym roku ogłosiliśmy, że stan danych od interakcji do kolejnego wyrenderowania (INP) zmieni się z eksperymentu na stan oczekiwania. W marcu 2024 r. podstawowy wskaźnik internetowy zastąpi Opóźnienie przy pierwszym działaniu (FID).

Nieustannie pracujemy nad nowymi interfejsami API, które pomagają programistom stron internetowych tworzyć niezwykle dynamiczne strony internetowe. Dlatego zespół Chrome obecnie prowadzi testy origin dla scheduler.yield, począwszy od wersji 115 Chrome. scheduler.yield to proponowany nowy dodatek do interfejsu API algorytmu szeregowania, który umożliwia łatwiejsze i lepsze przenoszenie kontroli z powrotem do wątku głównego niż metody, z których tradycyjnie się korzysta.

W przypadku plonów

Do obsługi zadań JavaScript używa modelu wykonywania do wykonania. Oznacza to, że gdy zadanie jest uruchamiane w wątku głównym, jest wykonywane przez tyle czasu, ile jest to konieczne do ukończenia. Po zakończeniu zadania kontrola zwraca się z powrotem do wątku głównego, dzięki czemu wątek główny może przetworzyć następne zadanie w kolejce.

Oprócz ekstremalnych przypadków, gdy zadanie nigdy się nie kończy (np. nieskończona pętla) zysk jest nieuniknionym aspektem logiki planowania zadań JavaScriptu. Będzie, ale chodzi tylko o to, kiedy – i wcześniej, tym lepiej. Gdy wykonywanie zadań trwa zbyt długo (dokładnie 50 milisekund), są one uznawane za długie zadania.

Długie zadania są przyczyną słabej responsywności strony, ponieważ opóźniają one reagowanie przeglądarki na dane wejściowe użytkownika. Im częściej występują długie zadania (i im dłużej działają), tym większe prawdopodobieństwo, że użytkownicy mają wrażenie, że strona działa wolno, a nawet wydaje się, że całkowicie nie działa.

Jednak to, że Twój kod rozpoczyna zadanie w przeglądarce, nie oznacza, że musisz czekać na zakończenie tego zadania, zanim kontrola wróci do wątku głównego. Możesz poprawić czas reagowania na dane wejściowe użytkownika na stronie, jawnie podporządkowując je zadaniu, aby zostało ono wykonane przy następnej dostępnej możliwości. Dzięki temu inne zadania uzyskają czas w wątku głównym wcześniej, niż musiałyby czekać na zakończenie długich zadań.

Ilustracja pokazująca, jak podzielenie zadania może usprawnić reagowanie na dane wejściowe. U góry długie zadanie blokuje działanie modułu obsługi zdarzeń do momentu jego ukończenia. Zadanie posegmentowane na dole umożliwia działanie modułu obsługi zdarzeń szybciej, niż byłoby to w Twoim przypadku.
Wizualizacja przywracania kontroli do wątku głównego. U góry generowanie zysku odbywa się tylko po wykonaniu zadania do końca, co oznacza, że wykonanie zadań może potrwać dłużej, zanim kontrola z powrotem do wątku głównego zostanie przywrócona. Na koniec generowanie zysku odbywa się bezpośrednio, dzieląc długie zadanie na kilka mniejszych. Dzięki temu interakcje użytkowników przebiegają szybciej, co poprawia responsywność danych wejściowych i liczbę INP.

Gdy wyraźnie wyrażasz zgodę, informujesz przeglądarkę: „Rozumiem, że to, co ja, rozumiem, może zająć trochę czasu i nie chcę, abyście wykonywali wszystkie te czynności, zanim odpowiesz na dane wejściowe użytkownika lub inne, które mogą być ważne”. Jest to przydatne narzędzie w zestawie narzędzi dla programistów, które może znacząco poprawić wrażenia użytkownika.

Problem z bieżącymi strategiami generowania zysków

Typowa metoda generowania korzysta z metody setTimeout z wartością czasu oczekiwania 0. To działa, ponieważ wywołanie zwrotne przekazane do setTimeout przenosi pozostałe zadania do innego zadania, które zostanie umieszczone w kolejce do kolejnego wykonania. Zamiast czekać, aż przeglądarka samo w sobie wykona działanie, mówisz „podzielmy ten kawałek pracy na mniejsze części”.

Uzyskiwanie korzyści przy użyciu funkcji setTimeout niesie jednak potencjalnie niepożądany efekt uboczny: praca, która następuje po punkcie zysku, wraca na koniec kolejki zadań. Zadania zaplanowane na podstawie interakcji użytkowników nadal będą w oczekiwanym terminie na początku kolejki, ale reszta zadań do wykonania po bezpośrednim wywołaniu może zostać opóźniona przez inne zadania z konkurencyjnych źródeł, które zostały umieszczone w kolejce przed nią.

Aby zobaczyć, jak to działa, wypróbuj tę wersję demonstracyjną błędu lub poeksperymentuj z nią w umieszczonej poniżej wersji. Wersja demonstracyjna składa się z kilku przycisków, które można kliknąć, oraz pola pod nimi, które rejestrują uruchomione zadania. Po przejściu na stronę wykonaj te czynności:

  1. Kliknij górny przycisk Okresowo uruchamiaj zadania, aby zaplanować regularne uruchamianie zadań blokujących. Gdy klikniesz ten przycisk, dziennik zadań zostanie zapełniony kilkoma komunikatami o treści Rankowanie zadania blokowania za pomocą setInterval.
  2. Następnie kliknij przycisk Pętla uruchamiania, w której każdej iteracji pojawia się setTimeout.

W polu na dole prezentacji zobaczysz coś takiego:

Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval

Te dane wyjściowe pokazują „koniec kolejki zadań”. zachowanie, które występuje przy zwracaniu wartości z parametrem setTimeout. Pętla uruchamiająca przetwarza 5 elementów i generuje polecenie setTimeout po przetworzeniu każdego z nich.

To ilustruje typowy problem występujący w internecie: nierzadko, jeśli skrypt, zwłaszcza zewnętrzny, rejestruje funkcję licznika czasu, która działa w określonym przedziale czasu. „Koniec kolejki zadań” zachowanie, które zapewnia generowanie przy użyciu funkcji setTimeout, oznacza, że zadania z innych źródeł zadań mogą być umieszczane w kolejce przed pozostałymi zadaniami, które pętla musi wykonać po uzyskaniu zwrotu.

W zależności od aplikacji może to być pożądany rezultat, ale w wielu przypadkach jest to powód, dla którego deweloperzy mogą niechętnie zrezygnować z kontroli nad głównym wątkiem. Zysk jest dobry, ponieważ interakcje użytkowników mogą zachodzić szybciej, ale umożliwiają również zajęcie się działaniami innych osób, które nie są użytkownikami, aby zająć się głównym wątkiem. To prawdziwy problem, ale aplikacja scheduler.yield może go rozwiązać.

Wejdź: scheduler.yield

Funkcja scheduler.yield jest dostępna za flagą jako eksperymentalna funkcja platformy internetowej od wersji Chrome 115. Możesz mieć wątpliwości: „dlaczego potrzebuję specjalnej funkcji, aby uzyskać wyniki, gdy funkcja setTimeout już to robi?”

Warto zauważyć, że uzyskanie zysku nie było celem projektowania w zakresie setTimeout, lecz korzystnym efektem ubocznym umożliwiającym zaplanowanie wywołania zwrotnego w późniejszym terminie, nawet jeśli limit czasu został określony na 0. Warto jednak pamiętać, że uzyskanie zgodności z zasadą setTimeout powoduje przeniesienie pozostałej pracy w tył kolejki. Domyślnie scheduler.yield wysyła pozostałe zadania na początek kolejki. Oznacza to, że praca, którą chcesz wznowić od razu po uzyskaniu zysku, nie zajmie się zadaniami z innych źródeł (z wyjątkiem interakcji użytkowników).

scheduler.yield to funkcja, która zwraca treść do wątku głównego i zwraca Promise po wywołaniu. Oznacza to, że możesz go await w funkcji async:

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

Aby zobaczyć, jak działa scheduler.yield, wykonaj te czynności:

  1. Wejdź na chrome://flags.
  2. Włącz eksperyment Funkcje eksperymentalnej platformy internetowej. Po wykonaniu tej czynności może być konieczne ponowne uruchomienie Chrome.
  3. Otwórz stronę demonstracyjną lub skorzystaj z umieszczonej poniżej listy wersji.
  4. Kliknij górny przycisk Okresowo uruchamiaj zadania.
  5. Na koniec kliknij przycisk Pętla uruchamiania, gdzie przy każdej iteracji pojawia się scheduler.yield.

Dane wyjściowe w polu u dołu strony będą wyglądać mniej więcej tak:

Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval

W przeciwieństwie do wersji demonstracyjnej, która generuje użycie funkcji setTimeout, widać, że pętla – mimo że występuje po każdej iteracji – nie przenosi pozostałej pracy na koniec kolejki, tylko na początek. Rozwiązanie to oferuje to, co najlepsze w obu przypadkach: możesz zwiększyć responsywność strony internetowej na podstawie danych wejściowych, ale masz też pewność, że praca, którą chcesz dokończyć po uzyskaniu wyników, nie pojawi się z opóźnieniem.

Wypróbuj tę funkcję!

Jeśli scheduler.yield wydaje Ci się ciekawy i chcesz go wypróbować, od wersji 115 Chrome możesz to zrobić na 2 sposoby:

  1. Jeśli chcesz poeksperymentować z usługą scheduler.yield lokalnie, wpisz chrome://flags na pasku adresu Chrome i wybierz Włącz z menu w sekcji Eksperymentalne funkcje platformy internetowej. Dzięki temu scheduler.yield (i inne funkcje eksperymentalne) będą dostępne tylko w Twojej instancji Chrome.
  2. Jeśli chcesz włączyć scheduler.yield dla rzeczywistych użytkowników Chromium w publicznie dostępnym źródle, musisz zarejestrować się w ramach testowania origin scheduler.yield. Dzięki temu możesz bezpiecznie eksperymentować z proponowanymi funkcjami przez określony czas, a zespół Chrome zyskuje cenne informacje na temat ich wykorzystania w terenie. Więcej informacji na temat testowania origin znajdziesz w tym przewodniku.

Sposób korzystania z funkcji scheduler.yield – mimo obsługi przeglądarek, które jej nie stosują – zależy od Twoich celów. Możesz użyć oficjalnego kodu polyfill. Kod polyfill jest przydatny, gdy:

  1. Używasz już scheduler.postTask w swojej aplikacji do planowania zadań.
  2. Chcesz mieć możliwość wyznaczania zadań i uzyskiwania priorytetów.
  3. Chcesz mieć możliwość anulowania zadań lub zmiany ich priorytetów za pomocą klasy TaskController dostępnej w interfejsie scheduler.postTask API.

Jeśli to nie opisuje Twojej sytuacji, kod polyfill może nie być dla Ciebie przeznaczony. W takim przypadku możesz przywrócić własną kreację zastępczą na kilka sposobów. Pierwsza metoda używa parametru scheduler.yield, jeśli jest dostępna, ale w przeciwnym razie korzysta z metody setTimeout:

// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

Może się to udać, ale jak się pewnie spodziewasz, przeglądarki, które nie obsługują interfejsu scheduler.yield, będą wyświetlać reklamy bez „frontu kolejki”. zachowanie użytkownika. Jeśli oznacza to, że w ogóle nie chcesz uzyskiwać zysku, możesz wypróbować inną metodę wykorzystującą metodę scheduler.yield (o ile jest dostępna). W przeciwnym razie nie uzyskasz żadnych wyników:

// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to nothing:
  return;
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

scheduler.yield to ciekawy dodatek do interfejsu API algorytmu szeregowania. Mamy nadzieję, że ułatwi programistom poprawianie responsywności w porównaniu z obecnymi strategiami generowania zysków. Jeśli interfejs scheduler.yield wydaje Ci się przydatny, weź udział w naszych badaniach, aby go ulepszyć, i prześlij opinię o tym, jak można go jeszcze ulepszyć.

Baner powitalny z filmu Unsplash, Jonathan Allison.