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 dołożył wszelkich starań, aby pomóc deweloperom w tym zakresie. 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 wysiłków zmierzających do udostępniania 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 nowa funkcja interfejsu Scheduler API, która umożliwia łatwiejszy i lepszy sposób oddania kontroli nad wątkiem głównemu niż metody tradycyjnie stosowane.
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 zadania trwają zbyt długo (czyli dłużej niż 50 ms), są 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 są wykonywane, tym większe prawdopodobieństwo, że użytkownicy będą mieli wrażenie, że strona działa wolno lub wręcz się zawiesza.
Jednak fakt, że Twój kod uruchamia zadanie w przeglądarce, nie oznacza, że musisz czekać, aż to zadanie się zakończy, zanim kontrola zostanie przekazana z powrotem do głównego wątku. 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 mogą szybciej uzyskać czas na wątku głównym niż wtedy, gdy musiałyby czekać na zakończenie długich zadań.
Gdy wyraźnie zrezygnujesz, mówisz przeglądarce: „Rozumiem, że wykonanie tej pracy może zająć trochę czasu, i nie chcę, abyś musiał wszystko zrobić, zanim zareagujesz na dane wejściowe użytkownika lub inne zadania, które też 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”.
Jednak przekazanie kontroli za pomocą funkcji setTimeout
może mieć niepożądany efekt uboczny: zadania, które pojawiają się po punkcie przekazania kontroli, trafią 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:
- 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 komunikatów o treści Wykonano zadanie blokowania z użyciem
setInterval
. - 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
Dane wyjściowe pokazują zachowanie „końca kolejki zadań”, które występuje po zwróceniu wartości setTimeout
. Wykonywana pętla przetwarza 5 elementów i zwraca wartość 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 użycia funkcji 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 z użycia funkcji.
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
scheduler.yield
jest dostępna jako eksperymentalna funkcja platformy internetowej od wersji 115 przeglądarki Chrome. Możesz się zastanawiać, dlaczego potrzebujesz specjalnej funkcji yield, skoro setTimeout
już to robi.
Warto zauważyć, że rezygnacja nie była celem projektu setTimeout
, ale raczej miłym efektem ubocznym zaplanowania wywołania zwrotnego do wykonania w przyszłości, nawet przy określonej wartości limitu czasu 0
. Należy jednak pamiętać, że przekazanie kontroli setTimeout
powoduje przesunięcie pozostałych zadań do końca 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
:
- Wejdź na
chrome://flags
. - Włącz eksperyment Eksperymentalne funkcje platformy internetowej. Po wykonaniu tej czynności może być konieczne ponowne uruchomienie Chrome.
- Otwórz stronę demonstracyjną lub skorzystaj z umieszczonej poniżej wersji demonstracyjnej.
- Kliknij górny przycisk Uruchamiaj zadania okresowo.
- Na koniec kliknij przycisk Uruchom pętlę, zwracając
scheduler.yield
w każdej 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:
- Jeśli chcesz eksperymentować z
scheduler.yield
lokalnie, wpiszchrome://flags
na pasku adresu Chrome i w menu w sekcji Eksperymentalne funkcje platformy internetowej wybierz Włącz. Dzięki temu funkcjascheduler.yield
(i wszystkie inne funkcje eksperymentalne) będą dostępne tylko w Twojej instancji Chrome. - Jeśli chcesz włączyć
scheduler.yield
dla prawdziwych użytkowników Chromium w publicznie dostępnej domenie, musisz zarejestrować się w ramach testowania originscheduler.yield
. Dzięki temu możesz bezpiecznie eksperymentować z proponowanymi funkcjami przez określony czas. Zespół Chrome zyska też cenne informacje o tym, jak te funkcje są używane w praktyce. Więcej informacji o tym, jak działają okresy próbne pochodzenia, znajdziesz w tym przewodniku.
Sposób korzystania z tagu scheduler.yield
(przy jednoczesnym zachowaniu obsługi w przypadku przeglądarek, które go nie obsługują) 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:
- Korzystasz już z usługi
scheduler.postTask
w aplikacji do planowania zadań. - Chcesz mieć możliwość ustawiania priorytetów zadań i wyników.
- Chcesz mieć możliwość anulowania zadań lub zmiany ich priorytetów za pomocą klasy
TaskController
oferowanej przez interfejs APIscheduler.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 to zadziałać, ale jak się zapewne domyślasz, przeglądarki, które nie obsługują scheduler.yield
, nie będą się zachowywać jak „pierwsze w kolejce”. 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 scheduler.yield
wydaje Ci się przydatnym interfejsem API, 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.