Websites zu entwickeln, die schnell auf Nutzereingaben reagieren, ist eine der größten Herausforderungen im Bereich der Web-Performance. 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. Er soll im März 2024 „First Input Delay“ (FID) als Core Web Vital ersetzen.
Das Chrome-Team arbeitet kontinuierlich daran, neue APIs bereitzustellen, mit denen Webentwickler ihre Websites so schnell wie möglich machen können. Derzeit wird ab Chrome-Version 115 ein Ursprungstest für scheduler.yield durchgeführt. scheduler.yield ist eine vorgeschlagene neue Ergänzung der Scheduler API, mit der die Steuerung einfacher und besser an den Hauptthread zurückgegeben werden kann als mit den bisherigen Methoden.
Zur Rückgabe
JavaScript verwendet das Run-to-Completion-Modell für 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 an den Hauptthread zurückgegeben , 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 die Rückgabe ein unvermeidlicher Aspekt der Aufgabenplanung von JavaScript. Sie wird auftreten, es ist nur eine Frage des Zeitpunkts, und früher ist besser als später. Wenn die Ausführung von Aufgaben zu lange dauert (mehr als 50 Millisekunden), gelten sie als lange Aufgaben.
Lange Aufgaben führen zu einer schlechten Reaktionsfähigkeit der Seite, da sie die Fähigkeit des Browsers verzögern, auf Nutzereingaben zu reagieren. Je häufiger lange Aufgaben auftreten und je länger sie ausgeführt werden, desto wahrscheinlicher ist es, dass Nutzer den Eindruck haben, dass die Seite langsam ist oder sogar ganz defekt ist.
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 Reaktionsfähigkeit auf Nutzereingaben auf einer Seite verbessern, indem Sie die Steuerung explizit in einer Aufgabe zurückgeben. Dadurch wird die Aufgabe unterbrochen und bei der nächsten verfügbaren Gelegenheit abgeschlossen. So können andere Aufgaben früher im Hauptthread ausgeführt werden, als wenn sie warten müssten, bis lange Aufgaben abgeschlossen sind.
Wenn Sie die Steuerung explizit zurückgeben, sagen Sie dem Browser: „Ich weiß, dass die Arbeit, die ich gleich erledigen werde, eine Weile dauern kann. Ich möchte nicht, dass Sie all diese Arbeit erledigen müssen, bevor Sie auf Nutzereingaben oder andere wichtige Aufgaben reagieren.“ Es ist ein wertvolles Tool für Entwickler, das die Nutzerfreundlichkeit erheblich verbessern kann.
Das Problem mit aktuellen Rückgabestrategien
Eine gängige Methode zur Rückgabe verwendet setTimeout mit einem Timeoutwert von 0. Das funktioniert, weil der an setTimeout übergebene Callback die verbleibende Arbeit in eine separate Aufgabe verschiebt, die für die spätere Ausführung in die Warteschlange gestellt wird. Anstatt darauf zu warten, dass der Browser die Steuerung selbst zurückgibt, sagen Sie: „Unterteilen Sie diesen großen Arbeitsblock in kleinere Teile.“
Die Rückgabe mit setTimeout hat jedoch einen potenziell unerwünschten Nebeneffekt: Die Arbeit, die nach dem Rückgabepunkt erfolgt, wird am Ende der Aufgabenwarteschlange platziert. Aufgaben, die durch Nutzerinteraktionen geplant werden, werden wie gewohnt am Anfang der Warteschlange platziert. Die verbleibende Arbeit, die Sie nach der expliziten Rückgabe erledigen wollten, kann jedoch durch andere Aufgaben aus konkurrierenden Quellen, die vor ihr in die Warteschlange gestellt wurden, weiter verzögert werden.
Um dies in Aktion zu sehen, probieren Sie diese Codepen-Demo aus oder experimentieren Sie mit der folgenden eingebetteten Version. 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 folgende Aktionen aus:
- Klicken Sie auf die obere Schaltfläche Run tasks periodically (Aufgaben regelmäßig ausführen). Dadurch werden blockierende Aufgaben geplant, die in regelmäßigen Abständen ausgeführt werden. Wenn Sie auf diese Schaltfläche klicken, werden im Aufgabenprotokoll mehrere Meldungen mit dem Text Ran blocking task with
setIntervalangezeigt. - Klicken Sie dann auf die Schaltfläche Run loop, yielding with
setTimeouton each iteration (Schleife ausführen, Rückgabe mitsetTimeoutbei jeder Iteration).
Im Feld unten in der Demo wird etwa 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 am Ende der Aufgabenwarteschlange, das bei der Rückgabe mit setTimeout auftritt. Die ausgeführte Schleife verarbeitet fünf Elemente und gibt die Steuerung nach der Verarbeitung jedes Elements mit setTimeout zurück.
Dies veranschaulicht ein häufiges Problem im Web: Es ist nicht ungewöhnlich, dass ein Skript, insbesondere ein Drittanbieterskript, eine Timerfunktion registriert, die in einem bestimmten Intervall Arbeit ausführt. Das Verhalten am Ende der Aufgabenwarteschlange, das bei der Rückgabe mit setTimeout auftritt, bedeutet, dass Arbeit aus anderen Aufgabenquellen vor der verbleibenden Arbeit in die Warteschlange gestellt werden kann, die die Schleife nach der Rückgabe erledigen muss.
Je nach Anwendung kann dies ein wünschenswertes Ergebnis sein oder nicht. In vielen Fällen ist dieses Verhalten jedoch der Grund dafür, dass Entwickler zögern, die Steuerung des Hauptthreads so schnell aufzugeben. Die Rückgabe ist gut, weil Nutzerinteraktionen früher ausgeführt werden können, aber auch andere Arbeiten, die keine Nutzerinteraktionen sind, können im Hauptthread ausgeführt werden. Das ist ein echtes Problem, aber scheduler.yield kann helfen, es zu lösen.
scheduler.yield
scheduler.yield ist seit Chrome-Version 115 als experimentelles Webplattform-Feature hinter einem Flag verfügbar. Sie fragen sich vielleicht, warum Sie eine spezielle Funktion für die Rückgabe benötigen, wenn setTimeout das bereits erledigt.
Es ist wichtig zu wissen, dass die Rückgabe kein Designziel von setTimeout war, sondern eher ein angenehmer Nebeneffekt bei der Planung eines Callbacks, der zu einem späteren Zeitpunkt ausgeführt werden soll – auch wenn ein Timeoutwert von 0 angegeben ist. Wichtiger ist jedoch, dass die Rückgabe mit setTimeout die verbleibende Arbeit am Ende der Aufgabenwarteschlange platziert. Standardmäßig platziert scheduler.yield die verbleibende Arbeit am Anfang der Warteschlange. Das bedeutet, dass die Arbeit, die Sie unmittelbar nach der Rückgabe fortsetzen wollten, nicht hinter Aufgaben aus anderen Quellen zurückgestellt wird (mit Ausnahme von Nutzerinteraktionen).
scheduler.yield ist eine Funktion, die die Steuerung an den Hauptthread zurückgibt und bei Aufruf ein Promise zurückgibt. Das bedeutet, dass Sie sie in einer async-Funktion await können:
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
So sehen Sie scheduler.yield in Aktion:
- Rufen Sie
chrome://flagsauf. - Aktivieren Sie das Experiment Experimental Web Platform features (Experimentelle Webplattform-Features). Möglicherweise müssen Sie Chrome danach neu starten.
- Rufen Sie die Demoseite auf oder verwenden Sie die folgende eingebettete Version nach dieser Liste.
- Klicken Sie auf die obere Schaltfläche Run tasks periodically (Aufgaben regelmäßig ausführen).
- Klicken Sie abschließend auf die Schaltfläche Run loop, yielding with
scheduler.yieldon each iteration (Schleife ausführen, Rückgabe mitscheduler.yieldbei jeder Iteration).
Die Ausgabe im Feld unten auf der Seite sieht 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 die Steuerung mit setTimeout zurückgegeben wird, sehen Sie, dass die Schleife die verbleibende Arbeit nicht am Ende der Warteschlange platziert, sondern am Anfang – auch wenn die Steuerung nach jeder Iteration zurückgegeben wird. So erhalten Sie das Beste aus beiden Welten: Sie können die Steuerung zurückgeben, um die Reaktionsfähigkeit bei Eingaben auf Ihrer Website zu verbessern, aber auch dafür sorgen, dass die Arbeit, die Sie nach der Rückgabe erledigen wollten, nicht verzögert wird.
Probieren Sie es aus!
Wenn scheduler.yield für Sie interessant klingt und Sie es ausprobieren möchten, haben Sie ab Chrome-Version 115 zwei Möglichkeiten:
- Wenn Sie
scheduler.yieldlokal testen möchten, geben Siechrome://flagsin die Adressleiste von Chrome ein und wählen Sie im Drop-down-Menü im Abschnitt Experimental Web Platform Features (Experimentelle Webplattform-Features) die Option Aktivieren aus. Dadurch wirdscheduler.yield(und alle anderen experimentellen Features) nur in Ihrer Chrome-Instanz verfügbar. - Wenn Sie
scheduler.yieldfür echte Chromium-Nutzer in einem öffentlich zugänglichen Ursprung aktivieren möchten, müssen Sie sich für denscheduler.yieldUrsprungstest anmelden. So können Sie vorgeschlagene Features für einen bestimmten Zeitraum sicher testen und das Chrome-Team erhält wertvolle Einblicke in die Verwendung dieser Features in der Praxis. Weitere Informationen zur Funktionsweise von Ursprungstests finden Sie in dieser Anleitung.
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.postTaskbereits in Ihrer Anwendung, um Aufgaben zu planen. - Sie möchten Prioritäten für Aufgaben und die Rückgabe festlegen können.
- Sie möchten Aufgaben mit der Klasse
TaskControllerderscheduler.postTaskAPI abbrechen oder neu priorisieren können.
Wenn dies nicht auf Ihre Situation zutrifft, ist das Polyfill möglicherweise nicht das Richtige für Sie. In diesem Fall können Sie auf verschiedene Weise ein eigenes Fallback erstellen. Der erste Ansatz verwendet scheduler.yield, wenn es verfügbar ist, und greift andernfalls auf setTimeout zurück:
// 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, geben Browser, die scheduler.yield nicht unterstützen, die Steuerung zurück, ohne dass die verbleibende Arbeit am Anfang der Warteschlange platziert wird. Wenn Sie die Steuerung lieber gar nicht zurückgeben möchten, können Sie einen anderen Ansatz verwenden, bei dem scheduler.yield verwendet wird, wenn es verfügbar ist, andernfalls aber keine Rückgabe erfolgt:
// 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 Rückgabestrategien. Wenn scheduler.yield für Sie eine nützliche API zu sein scheint, 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.