Websites zu entwickeln, die schnell auf Nutzereingaben reagieren, ist einer der schwierigsten Aspekte der Webleistung. Das Chrome-Team arbeitet intensiv daran, Webentwicklern dabei zu helfen, diese Herausforderung zu meistern. Erst in diesem Jahr wurde angekündigt, dass der Messwert „Interaction to Next Paint“ (INP) den Status „Experimentell“ verliert und in den Status „Ausstehend“ übergeht. Im März 2024 wird er First Input Delay (FID) als Core Web Vital ersetzen.
Das Chrome-Team arbeitet kontinuierlich daran, neue APIs bereitzustellen, mit denen Webentwickler ihre Websites so flüssig wie möglich gestalten können. Derzeit führt das Team einen Ursprungstest für scheduler.yield
durch, der ab Chrome-Version 115 verfügbar ist. scheduler.yield
ist ein vorgeschlagener neuer Bestandteil der Scheduler API, der eine einfachere und bessere Möglichkeit bietet, die Kontrolle an den Hauptthread zurückzugeben, als die bisher verwendeten Methoden.
Beim Ausweichen
In JavaScript werden Aufgaben mit dem Run-to-Completion-Modell ausgeführt. Wenn also eine Aufgabe im Hauptthread ausgeführt wird, dauert dies so lange, wie es für die Ausführung erforderlich ist. Nach Abschluss einer Aufgabe wird die Steuerung an den Hauptthread zurückgegeben, damit dieser 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 Ausgeben ein unvermeidlicher Aspekt der Task-Planungslogik von JavaScript. Es wird passieren, es ist nur eine Frage des Wanns. Je früher, desto besser. Wenn Aufgaben zu lange dauern, genauer gesagt mehr als 50 Millisekunden, werden sie als lange Aufgaben eingestuft.
Lange Aufgaben beeinträchtigen die Reaktionsfähigkeit der Seite, da sie die Reaktion des Browsers auf Nutzereingaben verzögern. Je häufiger lange Aufgaben auftreten und je länger sie laufen, desto wahrscheinlicher ist es, dass Nutzer den Eindruck haben, dass die Seite träge ist oder sogar ganz kaputt ist.
Nur weil Ihr Code eine Aufgabe im Browser startet, bedeutet das nicht, dass Sie warten müssen, bis diese Aufgabe abgeschlossen ist, bevor die Kontrolle an den Hauptthread zurückgegeben wird. Sie können die Reaktion auf Nutzereingaben auf einer Seite verbessern, indem Sie bei einer Aufgabe explizit einlenken. Dadurch wird die Aufgabe in mehrere Teile aufgeteilt, die bei der nächsten verfügbaren Gelegenheit abgeschlossen werden. So können andere Aufgaben früher Zeit im Hauptthread erhalten, als wenn sie auf das Ende langer Aufgaben warten müssten.
Wenn Sie explizit ausweichen, teilen Sie dem Browser mit: „Ich weiß, dass die Arbeit, die ich jetzt ausführen werde, etwas dauern kann. Ich möchte nicht, dass Sie alle diese Arbeit erledigen müssen, bevor Sie auf Nutzereingaben oder andere Aufgaben reagieren, die auch wichtig sein könnten.“ Es ist ein wertvolles Tool in der Toolbox eines Entwicklers, mit dem sich die Nutzerfreundlichkeit erheblich verbessern lässt.
Das Problem mit aktuellen Strategien zur Erzielung von Einnahmen
Eine gängige Methode zum Erzielen von ist die Verwendung von setTimeout
mit einem Zeitüberschreitungswert von 0
. Das funktioniert, weil der an setTimeout
übergebene Rückruf die verbleibende Arbeit in eine separate Aufgabe verschiebt, die für die nachfolgende Ausführung in die Warteschlange gestellt wird. Anstatt darauf zu warten, dass der Browser von selbst reagiert, sagen Sie: „Lass uns diesen großen Arbeitsblock in kleinere Teile aufteilen.“
Das Ausgeben von setTimeout
hat jedoch einen potenziell unerwünschten Nebeneffekt: Die Arbeit, die nach dem Yield-Punkt kommt, wird an das Ende der Aufgabenwarteschlange gestellt. Aufgaben, die durch Nutzerinteraktionen geplant wurden, werden weiterhin wie vorgesehen an den Anfang der Warteschlange gestellt. Die verbleibende Arbeit, die Sie nach dem expliziten Aussetzen erledigen wollten, kann jedoch durch andere Aufgaben aus konkurrierenden Quellen, die vor ihr in die Warteschlange gestellt wurden, weiter verzögert werden.
Diese Glitch-Demo oder die unten eingebettete Version veranschaulichen das Prinzip. Die Demo besteht aus einigen Schaltflächen, auf die Sie klicken können, und einem Feld darunter, in dem protokolliert wird, wann Aufgaben ausgeführt werden. Führen Sie auf der Seite die folgenden Aktionen aus:
- Klicken Sie oben auf die Schaltfläche Aufgaben regelmäßig ausführen. Dadurch werden blockierende Aufgaben in regelmäßigen Abständen ausgeführt. Wenn Sie auf diese Schaltfläche klicken, werden im Aufgabenprotokoll mehrere Meldungen mit dem Text Blockierungsaufgabe mit
setInterval
ausgeführt angezeigt. - Klicken Sie als Nächstes auf die Schaltfläche Schleife ausführen und bei jeder Iteration
setTimeout
zurückgeben.
Im Feld unten in der Demo wird ungefähr Folgendes angezeigt:
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 auftritt, wenn mit setTimeout
ein Yield ausgeführt wird. In der Schleife werden fünf Elemente verarbeitet und nach jeder Verarbeitung wird setTimeout
ausgegeben.
Das veranschaulicht ein häufiges Problem im Web: Es ist nicht ungewöhnlich, dass ein Script – insbesondere ein Script von Drittanbietern – eine Timerfunktion registriert, die in einem bestimmten Intervall ausgeführt wird. Das Verhalten „Ende der Aufgabenwarteschlange“, das beim Ausgeben mit setTimeout
auftritt, bedeutet, dass Aufgaben aus anderen Aufgabenquellen vor der verbleibenden Arbeit in die Warteschlange gestellt werden, die die Schleife nach dem Ausgeben ausführen muss.
Je nach Anwendung kann dies ein gewünschtes Ergebnis sein oder auch nicht. In vielen Fällen ist dieses Verhalten jedoch der Grund, warum Entwickler die Kontrolle über den Hauptthread nicht so leichtfertig abgeben. Das Ausgeben ist gut, weil Nutzerinteraktionen so früher ausgeführt werden können. Außerdem kann so auch andere Arbeit, die nicht mit Nutzerinteraktionen zusammenhängt, Zeit im Hauptthread erhalten. Das ist ein echtes Problem, aber scheduler.yield
kann Ihnen helfen, es zu lösen.
scheduler.yield
eingeben
scheduler.yield
ist seit Version 115 von Chrome als experimentelle Webplattformfunktion verfügbar. Sie könnten sich fragen: „Warum brauche ich eine spezielle Funktion für die Ausgabe, wenn setTimeout
das bereits tut?“
Das Ausgeben war kein Designziel von setTimeout
, sondern eher ein angenehmer Nebeneffekt beim Planen eines Callbacks, der zu einem späteren Zeitpunkt ausgeführt werden soll – auch wenn ein Zeitüberschreitungswert von 0
angegeben wurde. Wichtiger ist jedoch, dass verbleibende Arbeit durch Yielding mit setTimeout
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 nach dem Ausführen fortsetzen wollten, nicht von Aufgaben aus anderen Quellen verdrängt werden (mit der bemerkenswerten Ausnahme von Nutzerinteraktionen).
scheduler.yield
ist eine Funktion, die den Hauptthread anweist, die Ausführung zu beenden, und beim Aufrufen einen Promise
zurückgibt. Sie können sie also await
in einer async
-Funktion verwenden:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
So sehen Sie sich scheduler.yield
in Aktion an:
- Rufen Sie
chrome://flags
auf. - Aktivieren Sie den Test Experimentelle Funktionen der Webplattform. Möglicherweise müssen Sie Chrome danach neu starten.
- Rufen Sie die Demoseite auf oder verwenden Sie die eingebettete Version unten in dieser Liste.
- Klicken Sie oben auf die Schaltfläche Aufgaben regelmäßig ausführen.
- Klicken Sie abschließend auf die Schaltfläche Schleife ausführen und bei jeder Iteration mit
scheduler.yield
enden.
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, in der mit setTimeout
gewartet wird, sehen Sie hier, dass die Schleife die verbleibende Arbeit nicht ans Ende der Warteschlange sendet, sondern an den Anfang, auch wenn sie nach jeder Iteration pausiert. So haben Sie das Beste aus beiden Welten: Sie können die Eingabereaktion auf Ihrer Website verbessern, aber auch dafür sorgen, dass die Arbeit, die Sie nach dem Yielding erledigen wollten, 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 Version 115 von Chrome zwei Möglichkeiten:
- Wenn Sie
scheduler.yield
lokal ausprobieren möchten, geben Siechrome://flags
in die Adressleiste von Chrome ein und wählen Sie im Drop-down-Menü unter Experimentelle Webplattformfunktionen die Option Aktivieren aus.scheduler.yield
und alle anderen experimentellen Funktionen sind dann 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 für einen bestimmten Zeitraum sicher mit vorgeschlagenen Funktionen experimentieren und das Chrome-Team erhält wertvolle Einblicke in die praktische Nutzung dieser Funktionen. Weitere Informationen zur Funktionsweise von Herkunftstests finden Sie in diesem Leitfaden.
Wie Sie scheduler.yield
verwenden, um auch Browser zu unterstützen, die es nicht implementieren, hängt von Ihren Zielen ab. Sie können die offizielle polyfill verwenden. Die Polyfill ist nützlich, wenn Folgendes zutrifft:
- Sie verwenden bereits
scheduler.postTask
in Ihrer Anwendung, um Aufgaben zu planen. - Sie möchten Aufgaben- und Ertragsprioritäten festlegen können.
- Sie möchten Aufgaben über die
TaskController
-Klasse derscheduler.postTask
API abbrechen oder neu priorisieren können.
Wenn dies nicht auf Ihre Situation zutrifft, ist die Polyfill möglicherweise nicht für Sie geeignet. In diesem Fall haben Sie mehrere Möglichkeiten, einen eigenen Fallback einzurichten. Beim ersten Ansatz wird scheduler.yield
verwendet, wenn es verfügbar ist. Andernfalls wird setTimeout
verwendet:
// 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 vorstellen können, werden Browser, die scheduler.yield
nicht unterstützen, nicht an den Anfang der Warteschlange gestellt. Wenn Sie lieber gar keine Ergebnisse zurückgeben möchten, können Sie einen anderen Ansatz ausprobieren, bei dem scheduler.yield
verwendet wird, wenn es verfügbar ist, aber keine Ergebnisse zurückgegeben werden, wenn es nicht verfügbar ist:
// 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 spannende Ergänzung der Scheduler API, die es Entwicklern hoffentlich leichter machen wird, die Reaktionsfähigkeit zu verbessern als mit aktuellen Yielding-Strategien. Wenn Sie der Meinung sind, dass scheduler.yield
eine nützliche API ist, nehmen Sie bitte an unseren Studien teil, um uns bei der Verbesserung zu unterstützen. Geben Sie uns Feedback, wie sie noch verbessert werden könnte.
Hero-Image von Unsplash, von Jonathan Allison.