Przedstawiamy test źródła Scheduler.yield

Tworzenie stron internetowych, które szybko reagują na działania użytkowników, jest jednym z najtrudniejszych aspektów wydajności internetowej, a zespół Chrome ciężko pracuje nad spełnieniem tych wymagań. Jeszcze w tym roku ogłosiliśmy, że dane Interakcja z kolejnym wyrenderowaniem (INP) zostaną wycofane z eksperymentu na stan Oczekujący. W marcu 2024 r. ma zastąpić opóźnienie po pierwszej interakcji (FID) jako podstawowy wskaźnik internetowy.

Nieustannie pracujemy nad udostępnianiem nowych interfejsów API, dzięki którym deweloperzy stron internetowych mogą być maksymalnie chwytliwe. Dlatego zespół Chrome udostępnia obecnie wersję próbną przeglądarki scheduler.yield, która zaczyna się w wersji 115 Chrome. scheduler.yield to proponowany nowy dodatek do interfejsu API algorytmu szeregowania, który zapewnia zarówno łatwiejszy i lepszy sposób na przekazanie kontroli z powrotem do wątku głównego, niż metody, które były tradycyjnie stosowane.

Po uzyskaniu

Kod JavaScript do obsługi zadań wykorzystuje model „run-to-complete”. Oznacza to, że gdy zadanie jest uruchamiane w wątku głównym, trwa ono tak długo, jak jest to konieczne do ukończenia zadania. Po ukończeniu zadania element sterujący jest opuszczany z powrotem do wątku głównego, dzięki czemu wątek główny może przetworzyć następne zadanie w kolejce.

Poza skrajnymi przypadkami, w których zadanie nigdy się nie kończy – na przykład nieskończoną pętlą – zysk jest nieuniknionym aspektem logiki harmonogramu zadań JavaScript. Zdarzy się, to tylko kwestią kiedy, i później jest lepiej niż później. Zadania, które trwają zbyt długo (dokładnie ponad 50 milisekund), są uznawane za długie zadania.

Długie zadania powodują słabe reagowanie stron, ponieważ opóźniają zdolność przeglądarki do reagowania na dane wprowadzane przez użytkownika. Im częściej wykonywane są długie zadania i im dłużej trwa, tym większe jest prawdopodobieństwo, że użytkownicy mogą odnieść wrażenie, że strona działa wolniej lub działa prawidłowo.

Jednak to, że Twój kod uruchamia zadanie w przeglądarce, nie oznacza, że musisz czekać na jego zakończenie, zanim kontrola zostanie przekazana z powrotem do wątku głównego. Możesz poprawić reakcję na dane wprowadzane przez użytkownika na stronie, wykonując konkretne zadanie, co spowoduje jego podział i skończenie przy następnej dostępnej możliwości. Dzięki temu inne zadania mogą otrzymać czas w wątku głównym wcześniej, niż gdyby musiały czekać na zakończenie długich zadań.

Ilustracja pokazująca, jak podział zadania może przyspieszyć reagowanie na dane wejściowe. U góry długie zadanie blokuje uruchamianie modułu obsługi zdarzeń do momentu jego zakończenia. U dołu zadania podzielone na części umożliwia modułowi obsługi zdarzeń szybsze działanie, niż miałoby to miejsce.
wizualizacja przekazania kontroli z powrotem do wątku głównego; U góry, zadanie ma miejsce dopiero po jego zakończeniu, co oznacza, że wykonanie zadań może zająć więcej czasu, zanim zostanie zwrócona kontrola z powrotem do wątku głównego. W dolnej części proces uzyskiwania zwrotu jest jawny – to zadanie jest dzielone na kilka mniejszych. Umożliwia to szybsze wykonywanie interakcji użytkowników, co poprawia czas reakcji na dane wejściowe i wskaźnik INP.

Jeśli rezygnujesz, mówisz przeglądarce: „Hej, rozumiem, że praca, którą mam wykonać, może chwilę potrwać. Nie chcę, żebyście wykonywali całą tę pracę, zanim odpowiesz na opinie użytkownika lub inne ważne zadania. To cenne narzędzie, które może znacznie poprawić wygodę użytkowników.

Problem z aktualnymi strategiami zysku

Popularna metoda uzyskiwania używania setTimeout z wartością limitu czasu równą 0. Działa, ponieważ wywołanie zwrotne przekazane do setTimeout przeniesie pozostałą pracę do osobnego zadania, które zostanie umieszczone w kolejce do późniejszego wykonania. Zamiast czekać, aż przeglądarka zacznie sama działać, mówisz „podzielmy ten kawał pracy na mniejsze części”.

Jednak wywoływanie przy użyciu funkcji setTimeout może mieć potencjalnie niepożądany efekt uboczny: praca, która następuje po punkcie zysku, wróci na koniec kolejki zadań. Zadania zaplanowane przez użytkowników na podstawie interakcji użytkowników nadal będą wyświetlać się na początku kolejki, ale pozostałe czynności, które trzeba wykonać po jawnym powiadomieniu, mogą zostać opóźnione przez inne zadania z konkurencyjnych źródeł, które znajdowały się w kolejce.

Aby zobaczyć, jak to działa, wypróbuj tę wersję demonstracyjną Glitch lub poeksperymentuj z tą funkcją w umieszczonej poniżej wersji. Prezentacja składa się z kilku przycisków, które można kliknąć, oraz pola pod nimi, które rejestruje uruchomienie zadań. Po przejściu na stronę wykonaj te czynności:

  1. Kliknij górny przycisk Uruchamiaj zadania okresowo, co spowoduje zaplanowanie częstego uruchamiania zadań blokujących. Gdy klikniesz ten przycisk, dziennik zadań wypełni się kilkoma komunikatami o treści Uruchomienie zadania blokującego zadanie setInterval.
  2. Kliknij przycisk Uruchom pętlę, generując setTimeout przy każdej iteracji.

Pole u dołu wersji demonstracyjnej powinno wyglądać mniej więcej tak:

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 prezentują działanie „koniec kolejki zadań”, które występuje w przypadku wywołania funkcji setTimeout. Pętla, w której przetwarza 5 elementów, i generuje wartość setTimeout po przetworzeniu każdego z nich.

Pokazuje to typowy problem w internecie: nie jest niczym niezwykłym, że skrypt – w szczególności skrypt innej firmy – rejestruje funkcję licznika czasu, która działa w określonym przedziale czasu. Działanie „koniec kolejki zadań” związane z opcją setTimeout oznacza, że zadania z innych źródeł zadań mogą zostać umieszczone w kolejce przed pozostałymi zadaniami, które pętla ma wykonać po odblokowaniu.

W zależności od aplikacji wynik może być niepożądany, ale w wielu przypadkach to właśnie dlatego deweloperzy mogą niechętnie rezygnować z kontroli nad głównym wątkiem. Zyski są korzystne, ponieważ interakcje użytkowników mogą zaistnieć wcześniej, ale umożliwiają też pracę w wątku głównym innym osobom, które nie należą do niego. To prawdziwy problem, ale scheduler.yield może pomóc Ci go rozwiązać.

Wejdź do scheduler.yield

scheduler.yield jest dostępny od wersji 115 Chrome jako eksperymentalna funkcja platformy internetowej. Być może zastanawiasz się między innymi, dlaczego potrzebuję specjalnej funkcji, aby uzyskać funkcję setTimeout, która już z niej korzysta.

Warto zauważyć, że zyski nie były celem projektowym setTimeout, ale raczej miłym efektem ubocznym przy zaplanowaniu wywołania zwrotnego w późniejszym czasie – nawet przy określonej wartości czasu oczekiwania wynoszącego 0. Ważniejsze jest jednak to, że zadanie setTimeout powoduje wysłanie pozostałej pracy na tył kolejki zadań. Domyślnie scheduler.yield wysyła pozostałe zadania na pierwszą część kolejki. Oznacza to, że praca, którą chcesz wznowić od razu po uzyskaniu wyniku, nie spowoduje cofnięcia się do zadań z innych źródeł (z wyjątkiem interakcji użytkowników).

scheduler.yield to funkcja, która zwraca wartość do wątku głównego i po jej wywołaniu zwraca wartość Promise. Oznacza to, że możesz await go 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 użyj jej wersji umieszczonej pod tą listą.
  4. Kliknij górny przycisk Uruchamiaj zadania okresowo.
  5. Na koniec kliknij przycisk Uruchom pętlę, generując scheduler.yield przy 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 korzysta z funkcji setTimeout, widać, że pętla – mimo że kończy się po każdej iteracji – nie wysyła pozostałej pracy na tyle kolejki, a nie na początek. Łączy to zalety obu tych rozwiązań: możesz zwiększyć szybkość reagowania witryny na dane wejściowe, ale jednocześnie zadbać o to, by praca, którą chcesz zakończyć po uzyskaniu wyników, nie przyspieszyła działania.

Wypróbuj tę funkcję!

Jeśli usługa scheduler.yield wydaje Ci się interesująca i chcesz ją 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 i wpisz chrome://flags na pasku adresu Chrome, a następnie wybierz Włącz z menu w sekcji Eksperymentalne funkcje platformy internetowej. Dzięki temu usługa scheduler.yield (i wszelkie inne eksperymentalne funkcje) będzie dostępna tylko w Twoim wystąpieniu Chrome.
  2. Jeśli chcesz włączyć scheduler.yield dla prawdziwych użytkowników Chromium w źródle dostępnym publicznie, musisz zarejestrować się na test origin scheduler.yield. Dzięki temu możesz bezpiecznie eksperymentować z proponowanymi funkcjami przez określony czas, a zespół Chrome ma cenne informacje na temat ich wykorzystania w terenie. Więcej informacji na temat działania testowania origin znajdziesz w tym przewodniku.

To, jak korzystasz z usługi scheduler.yield, a jednocześnie obsługa przeglądarek, które jej nie obsługują, zależy od Twoich celów. Możesz użyć oficjalnego kodu polyfill. Kod polyfill jest przydatny w takich sytuacjach:

  1. Używasz już adresu scheduler.postTask w swojej aplikacji do planowania zadań.
  2. Musisz mieć możliwość określania priorytetów zadań i zysków.
  3. Chcesz mieć możliwość anulowania lub zmiany priorytetu zadań za pomocą klasy TaskController oferowanej przez interfejs API scheduler.postTask.

Jeśli Twój opis nie pasuje do Twojej sytuacji, kod polyfill może nie być dla Ciebie. W takim przypadku możesz wycofać własną kreację zastępczą na kilka sposobów. Pierwsza metoda używa atrybutu scheduler.yield, jeśli jest dostępny. W przeciwnym razie przywraca wartość 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:
  // ...
}

To może się udać, ale jak się pewnie domyślasz, przeglądarki, które nie obsługują scheduler.yield, wyświetlają się bez działania „początku kolejki”. Jeśli oznacza to, że wolisz w ogóle nie dać się nabrać, możesz wypróbować inną metodę, która korzysta z metody scheduler.yield, o ile jest dostępna, ale w ogóle nie pozwoli, jeśli nie:

// 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 ciekawe uzupełnienie API algorytmu szeregowania, które prawdopodobnie ułatwi deweloperom zwiększenie reagowania w porównaniu z obecnymi strategiami uzyskiwania zysków. Jeśli interfejs scheduler.yield wydaje się przydatny, weź udział w naszych badaniach, które pomogą nam go ulepszyć, i prześlij opinię na temat możliwych ulepszeń.

Baner powitalny z albumu Unsplash, autor: Jonathan Allison.