Die Entwicklung von Websites, die schnell auf Nutzereingaben reagieren, ist einer der anspruchsvollsten Aspekte der Webleistung. Das Chrome-Team arbeitet intensiv daran, Webentwicklern dabei zu helfen. Erst in diesem Jahr wurde angekündigt, dass der Messwert „Interaction to Next Paint“ (INP) vom experimentellen in den ausstehenden Status übergeht. Im März 2024 soll First Input Delay (FID) als Core Web Vital ersetzt werden.
Das Chrome-Team arbeitet kontinuierlich daran, neue APIs bereitzustellen, mit denen Webentwickler ihre Websites so schnell wie möglich gestalten können. Daher wird ab Chrome 115 ein Ursprungstest für scheduler.yield
durchgeführt. scheduler.yield
ist ein vorgeschlagener neuer Zusatz zur Scheduler API, der sowohl eine einfachere als auch eine bessere Möglichkeit bietet, die Steuerung an den Hauptthread zurückzugeben als die Methoden, auf die traditionell zurückgegriffen wurde.
Bei der Auslieferung
JavaScript verwendet das Run-to-Completion-Modell für die Verarbeitung von Aufgaben. Wenn eine Aufgabe im Hauptthread ausgeführt wird, wird sie so lange ausgeführt, bis sie abgeschlossen ist. Nach Abschluss einer Aufgabe wird die Steuerung wieder an den Hauptthread übergeben, sodass der Hauptthread die nächste Aufgabe in der Warteschlange verarbeiten kann.
Abgesehen von Extremfällen, in denen eine Aufgabe nie abgeschlossen wird, z. B. bei einer Endlosschleife, ist das Yielding ein unvermeidlicher Aspekt der Logik für die Aufgabenplanung von JavaScript. Es wird passieren, es ist nur eine Frage des Wann, und früher ist besser als später. Wenn die Ausführung von Aufgaben zu lange dauert, genauer gesagt länger als 50 Millisekunden, gelten sie als Long Tasks.
Lange Tasks sind eine Quelle für eine schlechte Reaktionsfähigkeit der Seite, da sie die Fähigkeit des Browsers verzögern, auf Nutzereingaben zu reagieren. Je häufiger und länger langwierige Aufgaben ausgeführt werden, desto wahrscheinlicher ist es, dass Nutzer den Eindruck haben, die Seite sei träge oder sogar defekt.
Nur weil Ihr Code eine Aufgabe im Browser startet, müssen Sie jedoch nicht warten, bis diese Aufgabe abgeschlossen ist, bevor die Steuerung an den Hauptthread zurückgegeben wird. Sie können die Reaktionsschnelligkeit auf Nutzereingaben auf einer Seite verbessern, indem Sie in einer Aufgabe explizit nachgeben. Dadurch wird die Aufgabe unterbrochen und kann bei der nächsten verfügbaren Gelegenheit abgeschlossen werden. Dadurch können andere Aufgaben früher im Hauptthread ausgeführt werden, als wenn sie auf den Abschluss langer Aufgaben warten müssten.

Wenn Sie explizit nachgeben, sagen Sie dem Browser: „Hey, ich weiß, dass die Arbeit, die ich gleich erledigen werde, eine Weile dauern könnte, und ich möchte nicht, dass du all diese Arbeit erledigen musst, bevor du auf Nutzereingaben oder andere wichtige Aufgaben reagierst.“ Es ist ein wertvolles Tool für Entwickler, mit dem sich die Nutzerfreundlichkeit erheblich verbessern lässt.
Das Problem mit aktuellen Ertragsstrategien
Eine gängige Methode zum Yielding von verwendet setTimeout
mit einem Zeitüberschreitungswert von 0
. Das funktioniert, weil durch den an setTimeout
übergebenen Callback die verbleibende Arbeit in eine separate Aufgabe verschoben wird, die für die spätere Ausführung in die Warteschlange gestellt wird. Anstatt darauf zu warten, dass der Browser von selbst nachgibt, sagen Sie: „Teile diesen großen Arbeitsblock in kleinere Teile auf.“
Das Yielding mit setTimeout
hat jedoch eine potenziell unerwünschte Nebenwirkung: Die Arbeit, die nach dem Yield-Punkt erfolgt, wird am Ende der Aufgabenwarteschlange ausgeführt. Aufgaben, die durch Nutzerinteraktionen geplant werden, werden weiterhin wie vorgesehen an den Anfang der Warteschlange gestellt. Die verbleibende Arbeit, die Sie nach dem expliziten Yielding ausführen wollten, kann jedoch durch andere Aufgaben aus konkurrierenden Quellen, die vor ihr in die Warteschlange gestellt wurden, weiter verzögert werden.
In dieser Codepen-Demo können Sie sich das in Aktion ansehen. Sie können auch mit der folgenden eingebetteten Version experimentieren. Die Demo besteht aus einigen Schaltflächen, auf die Sie klicken können, und einem Feld darunter, in dem protokolliert wird, wenn Aufgaben ausgeführt werden. Führen Sie auf der Seite folgende Aktionen aus:
- Klicken Sie auf die obere Schaltfläche mit der Aufschrift Aufgaben regelmäßig ausführen. Dadurch werden blockierende Aufgaben so geplant, dass sie in regelmäßigen Abständen ausgeführt werden. Wenn Sie auf diese Schaltfläche klicken, wird das Aufgabenprotokoll mit mehreren Meldungen gefüllt, in denen Blockierende Aufgabe mit
setInterval
ausgeführt steht. - Klicken Sie als Nächstes auf die Schaltfläche Run loop, yielding with
setTimeout
on each iteration (Schleife ausführen, bei jeder Iteration mitsetTimeout
zurückgeben).
Im Feld unten in der Demo steht dann etwa Folgendes:
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
Diese Ausgabe zeigt das Verhalten „Ende der Aufgabenwarteschlange“, das beim Yielding mit setTimeout
auftritt. Die Schleife verarbeitet fünf Elemente und gibt nach jedem Element setTimeout
aus.
Das ist ein häufiges Problem im Web: Es ist nicht ungewöhnlich, dass ein Script, insbesondere ein Drittanbieter-Script, eine Timerfunktion registriert, die in einem bestimmten Intervall ausgeführt wird. Das Verhalten „Ende der Aufgabenwarteschlange“, das mit der Übergabe mit setTimeout
einhergeht, bedeutet, dass Aufgaben aus anderen Aufgabenquellen vor den verbleibenden Aufgaben, die die Schleife nach der Übergabe ausführen muss, in die Warteschlange gestellt werden können.
Je nach Anwendung kann dies ein wünschenswertes Ergebnis sein oder nicht. In vielen Fällen ist dieses Verhalten der Grund, warum Entwickler zögern, die Kontrolle über den Hauptthread so schnell aufzugeben. Das Yielding ist gut, weil Nutzerinteraktionen früher ausgeführt werden können. Außerdem können auch andere Aufgaben, die nicht mit Nutzerinteraktionen zusammenhängen, auf dem Hauptthread ausgeführt werden. Das ist ein echtes Problem – aber scheduler.yield
kann helfen, es zu lösen.
scheduler.yield
eingeben
scheduler.yield
ist seit Chrome-Version 115 als experimentelle Webplattformfunktion hinter einem Flag verfügbar. Eine Frage, die Sie sich vielleicht stellen, ist: „Warum brauche ich eine spezielle Funktion für die Übergabe, wenn setTimeout
das bereits erledigt?“
Es ist wichtig zu beachten, dass die Übergabe kein Designziel von setTimeout
war, sondern eher ein angenehmer Nebeneffekt bei der Planung eines Rückrufs, der zu einem späteren Zeitpunkt ausgeführt werden soll – auch wenn ein Zeitüberschreitungswert von 0
angegeben ist. Wichtiger ist jedoch, dass durch die Übergabe mit setTimeout
die verbleibende Arbeit an das Ende der Aufgabenwarteschlange gesendet wird. Standardmäßig sendet scheduler.yield
verbleibende Arbeit an den Anfang der Warteschlange. Das bedeutet, dass Aufgaben, die Sie unmittelbar nach dem Yielding fortsetzen möchten, nicht hinter Aufgaben aus anderen Quellen (mit Ausnahme von Nutzerinteraktionen) zurückgestellt werden.
scheduler.yield
ist eine Funktion, die den Hauptthread freigibt und bei Aufruf ein Promise
zurückgibt. Sie können await
also in einer async
-Funktion verwenden:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
So sehen Sie scheduler.yield
in Aktion:
- Rufen Sie
chrome://flags
auf. - Aktivieren Sie den Test Experimentelle Webplattformfunktionen. Möglicherweise müssen Sie Chrome danach neu starten.
- Rufen Sie die Demoseite auf oder verwenden Sie die folgende eingebettete Version.
- Klicken Sie auf die obere Schaltfläche mit der Beschriftung Run tasks periodically (Aufgaben regelmäßig ausführen).
- Klicken Sie abschließend auf die Schaltfläche mit der Beschriftung Run loop, yielding with
scheduler.yield
on each iteration (Schleife ausführen, bei jeder Iteration mitscheduler.yield
unterbrechen).
Die Ausgabe im Feld unten auf der Seite sieht in etwa so aus:
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
Im Gegensatz zur Demo, bei der setTimeout
verwendet wird, wird die verbleibende Arbeit in der Schleife nicht ans Ende, sondern an den Anfang der Warteschlange verschoben, obwohl nach jeder Iteration ein Yield erfolgt. So können Sie die Reaktionsfähigkeit der Eingabe auf Ihrer Website verbessern, aber auch dafür sorgen, dass die Arbeit, die Sie nach dem Yielding erledigen möchten, nicht verzögert wird.
Probieren Sie es aus!
Wenn scheduler.yield
für Sie interessant ist und Sie es ausprobieren möchten, haben Sie ab Chrome-Version 115 zwei Möglichkeiten:
- Wenn Sie
scheduler.yield
lokal testen möchten, geben Siechrome://flags
in die Adressleiste von Chrome ein und wählen Sie im Drop-down-Menü im Bereich Experimental Web Platform Features die Option Aktivieren aus. Dadurch wirdscheduler.yield
(und alle anderen experimentellen Funktionen) nur in Ihrer Chrome-Instanz verfügbar. - Wenn Sie
scheduler.yield
für echte Chromium-Nutzer auf einem öffentlich zugänglichen Ursprung aktivieren möchten, müssen Sie sich für denscheduler.yield
-Ursprungstest registrieren. So können Sie vorgeschlagene Funktionen über einen bestimmten Zeitraum hinweg sicher testen und das Chrome-Team erhält wertvolle Informationen darüber, wie diese Funktionen in der Praxis verwendet werden. Weitere Informationen zu Origin Trials
Wie Sie scheduler.yield
verwenden und gleichzeitig Browser unterstützen, die es nicht implementieren, hängt von Ihren Zielen ab. Sie können das offizielle Polyfill verwenden. Das Polyfill ist nützlich, wenn Folgendes auf Ihre Situation zutrifft:
- Sie verwenden
scheduler.postTask
bereits in Ihrer Anwendung, um Aufgaben zu planen. - Sie möchten Prioritäten für Aufgaben und Yielding festlegen können.
- Sie möchten Aufgaben mit der
TaskController
-Klasse derscheduler.postTask
API abbrechen oder neu priorisieren können.
Wenn das nicht auf Ihre Situation zutrifft, ist das Polyfill möglicherweise nicht für Sie geeignet. In diesem Fall können Sie auf verschiedene Arten einen eigenen Fallback erstellen. Beim ersten Ansatz wird scheduler.yield
verwendet, sofern verfügbar. Andernfalls wird auf setTimeout
zurückgegriffen:
// 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:
// ...
}
Das kann funktionieren, aber wie Sie sich vielleicht denken können, wird bei Browsern, die scheduler.yield
nicht unterstützen, kein „Front of Queue“-Verhalten erzielt. Wenn Sie lieber gar keine Ertragsoptimierung durchführen möchten, können Sie einen anderen Ansatz verwenden, bei dem scheduler.yield
genutzt wird, sofern verfügbar. Andernfalls wird keine Ertragsoptimierung durchgeführt:
// 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
ist eine interessante Ergänzung der Scheduler API, die es Entwicklern hoffentlich einfacher macht, die Reaktionsfähigkeit zu verbessern als mit den aktuellen Yielding-Strategien. Wenn scheduler.yield
für Sie eine nützliche API ist, nehmen Sie bitte an unserer Studie teil, um sie zu verbessern, und geben Sie Feedback dazu, wie sie weiter verbessert werden könnte.
Hero-Image von Unsplash, von Jonathan Allison.