Przedstawiamy test źródła Scheduler.yield

Tworzenie witryn, które szybko reagują na dane wejściowe użytkowników, było jednym z najtrudniejszych aspektów wydajności stron internetowych. Zespół Chrome dokładał wszelkich starań, aby pomóc deweloperom w rozwiązaniu tego problemu. W tym roku ogłoszono, że dane dotyczące interakcji do kolejnego wyrenderowania (INP) przestaną być eksperymentalne i przejdą do statusu oczekujące. W marcu 2024 r. zastąpi on opóźnienie przy pierwszym działaniu (FID) jako podstawowy wskaźnik internetowy.

W ramach naszych ciągłych starań o udostępnianie nowych interfejsów API, które pomagają programistom stron internetowych w czynieniu ich jak najszybszymi, zespół Chrome prowadzi obecnie testy wersji próbnej 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.

O ustąpieniu

Kod JavaScript do wykonywania zadań stosuje model wykonywania do końca. Oznacza to, że gdy zadanie jest wykonywane w wątku głównym, trwa tak długo, jak jest to konieczne do jego ukończenia. Po zakończeniu zadania sterowanie zostaje zwrócone do wątku głównego, co pozwala wątkowi głównemu przetworzyć następne zadanie w kole.

Z wyjątkiem skrajnych przypadków, gdy zadanie nigdy się nie kończy (np. pętla nieskończona), zwracanie jest nieuniknionym aspektem logiki planowania zadań w JavaScript. To się stanie, tylko kwestia czasu. Lepiej wcześniej niż później. Gdy wykonywanie zadań trwa zbyt długo (dokładnie 50 milisekund), są one uznawane za długie zadania.

Długie zadania powodują słabą responsywność strony, ponieważ opóźniają reakcję 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 fakt, że Twój kod uruchamia zadanie w przeglądarce, nie oznacza, że musisz czekać, aż to zadanie się zakończy, zanim przekazanie sterowania do wątku głównego. Możesz poprawić responsywność na dane wprowadzane przez użytkownika na stronie, wyraźnie oddając zadanie, co spowoduje jego przerwanie i dokończenie przy następnej okazji. 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 podział zadania może ułatwić reagowanie na dane wejściowe. U góry długie zadanie blokuje działanie metody obsługi zdarzenia, dopóki nie zostanie ono ukończone. Zadanie posegmentowane na dole umożliwia działanie modułu obsługi zdarzeń szybciej, niż byłoby to w Twoim przypadku.
Wizualizacja oddania kontroli nad wątkiem głównemu. 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 cofnięta. Na dole zadanie jest oddawane w wyraźny sposób, dzieląc długie zadanie na kilka krótszych. Dzięki temu interakcje z użytkownikiem mogą być wykonywane wcześniej, co poprawia szybkość reakcji na dane wejściowe i wskaźnik 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 cenne narzędzie w arsenalu dewelopera, które może znacznie poprawić wygodę użytkowników.

Problemy z obecnymi strategiami uzyskiwania zysków

Typowa metoda uzyskiwania korzysta z funkcji setTimeout z wartością limitu czasu 0. Działa to, ponieważ funkcja wywołania zwrotnego przekazana do funkcji setTimeout przeniesie pozostałe zadanie do osobnego zadania, które zostanie umieszczone w kole do wykonania. Zamiast czekać, aż przeglądarka sama się podda, mówisz: „Podzielmy ten duży 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 przez interakcje z użytkownikiem nadal będą trafiać na początek kolejki, ale pozostałe zadania, które chcesz wykonać po wyraźnym oddaniu kontroli, mogą zostać opóźnione przez inne zadania z konkurencyjnych źródeł, które zostały dodane do kolejki wcześniej.

Aby zobaczyć, jak to działa, wypróbuj demo na Glitch lub eksperymentuj z umieszczoną poniżej wersją. Demonstracja składa się z kilku przycisków, które możesz kliknąć, oraz pola poniżej, w którym rejestrowane jest wykonanie zadań. Po przejściu na stronę wykonaj te czynności:

  1. Kliknij górny przycisk Uruchamiaj zadania okresowo, aby zaplanować uruchamianie zadań blokujących w określonych odstępach czasu. Po kliknięciu tego przycisku w dzienniku zadań pojawi się kilka wiadomości o treści Wykonano zadanie blokowania z użyciem setInterval.
  2. Następnie kliknij przycisk Wykonaj pętlę, zwracając setTimeout w każdej iteracji.

W polu u dołu strony zobaczysz komunikat podobny do tego:

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ń”, które występuje w przypadku uzyskania zwrotu z metody setTimeout. Pętla uruchamiająca przetwarza 5 elementów i generuje polecenie setTimeout po przetworzeniu każdego z nich.

Przykład ten ilustruje typowy problem w internecie: nie jest niczym niezwykłym, że skrypt, zwłaszcza zewnętrzny, rejestruje funkcję zegara, która wykonuje pracę w określonym odstępie czasu. Zachowanie „końca kolejki zadań” związane z zrezygnowaniem z setTimeout oznacza, że zadania z innych źródeł mogą zostać umieszczone w kolejce przed pozostałymi zadaniami, które pętla musi wykonać po zrezygnowaniu.

W zależności od aplikacji może to być pożądany efekt, ale w wielu przypadkach może być też powodem, dla którego deweloperzy nie chcą tak łatwo oddawać kontroli nad wątkiem głównym. Oddawanie zasobów jest dobre, ponieważ interakcje z użytkownikiem mogą się uruchomić wcześniej, ale pozwala też innym działaniom niewymagającym interakcji z użytkownikiem na uzyskanie czasu na wątku głównym. To prawdziwy problem, ale scheduler.yield może Ci pomóc 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 się zastanawiać, dlaczego potrzebujesz specjalnej funkcji yield, skoro 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. Należy jednak pamiętać, że przekazanie kontroli setTimeout spowoduje, że pozostała praca zostanie przesunęta na koniec kolejki zadań. Domyślnie scheduler.yield przesyła pozostałe zadania do początku kolejki. Oznacza to, że zadania, które chcesz wznowić natychmiast po oddaniu, nie będą odkładane na dalszy plan zadań z innych źródeł (z wyjątkiem interakcji z użytkownikami).

scheduler.yield to funkcja, która zwraca głównemu wątkowi wartość Promise. Oznacza to, że możesz await go użyć 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 Eksperymentalne funkcje 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 wersji demonstracyjnej.
  4. Kliknij górny przycisk Uruchamiaj zadania okresowo.
  5. Na koniec kliknij przycisk Uruchom pętlę, zwracając scheduler.yield na każdym iteracji.

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 zwraca wartość za pomocą funkcji setTimeout, możesz zauważyć, że pętla – mimo że zwraca wartość po każdej iteracji – nie wysyła pozostałej pracy na koniec kolejki, ale na jej początek. Dzięki temu możesz połączyć zalety obu rozwiązań: możesz stosować funkcję yield, aby poprawić szybkość działania formularzy na stronie, ale też mieć pewność, że zadanie, które chcesz ukończyć po użyciu tej funkcji, nie zostanie opóźnione.

Wypróbuj tę funkcję!

Jeśli funkcja scheduler.yield Cię interesuje i chcesz ją wypróbować, możesz to zrobić na 2 sposoby, począwszy od wersji 115 Chrome:

  1. Jeśli chcesz eksperymentować z scheduler.yield lokalnie, wpisz chrome://flags na pasku adresu w Chrome i w sekcji Eksperymentalne funkcje platformy internetowej kliknij Włącz. 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ć oficjalnej polyfill. Rozszerzenie polyfill jest przydatne, jeśli w Twoim przypadku występuje jeden z tych problemów:

  1. Używasz już scheduler.postTask w swojej aplikacji do planowania zadań.
  2. Chcesz mieć możliwość ustawiania priorytetów zadań i wyników.
  3. Chcesz mieć możliwość anulowania zadań lub zmiany ich priorytetów za pomocą klasy TaskController, którą udostępnia interfejs API scheduler.postTask.

Jeśli to nie Twój przypadek, polyfill może nie być dla Ciebie odpowiedni. W takim przypadku możesz wdrożyć własne rozwiązanie zastępcze na kilka sposobów. Pierwsze podejście używa funkcji scheduler.yield, jeśli jest dostępne, ale w przeciwnym razie korzysta z funkcji 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ą działać bez działań „z początku kolejki”. Jeśli wolisz w ogóle nie uzyskiwać wartości zwracanej, możesz spróbować innego podejścia, które wykorzystuje funkcję scheduler.yield, jeśli jest dostępna, ale nie zwraca wartości zwracanej, jeśli nie jest dostępne:

// 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 ekscytujące uzupełnienie interfejsu Scheduler API, które, jak mamy nadzieję, ułatwi programistom zwiększenie szybkości działania w porównaniu z obecnymi strategiami generowania zysków. Jeśli interfejs scheduler.yield wydaje Ci się przydatny, weź udział w naszych badaniach, aby pomóc nam go ulepszyć, i przekaż opinię na temat tego, jak można go jeszcze bardziej udoskonalić.

Baner powitalny z Unsplash autorstwa Jonathana Allisona.