Websites zu erstellen, die schnell auf Nutzereingaben reagieren, ist einer der herausforderndsten Aspekte der Webleistung. Das Chrome-Team hat hart daran gearbeitet, Webentwicklern dabei zu helfen. 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 extremen Fä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 führen zu einer schlechten Reaktionszeit von Seiten, 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 beendet 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 auf den Hauptthread zugreifen, als wenn sie auf das Ende langer Aufgaben warten müssten.
Wenn Sie explizit einlenken, 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 alles 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 alleine nachlässt, sagen wir: „Lassen Sie uns diesen großen Teil der Arbeit in kleinere Teile aufteilen“.
Die Ausgabe mit setTimeout
hat jedoch einen möglicherweise unerwünschten Nebeneffekt: Die Arbeit, die nach dem Ertragspunkt 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.
Um dies in Aktion zu sehen, probiere diese Glitch-Demo aus oder experimentiere in der eingebetteten Version unten damit. 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 auf die oberste Schaltfläche Tasks regelmäßig ausführen. Dadurch werden blockierende Aufgaben geplant, die regelmäßig ausgeführt werden. Wenn Sie auf diese Schaltfläche klicken, werden im Aufgabenprotokoll mehrere Meldungen mit dem Text Blockierungsaufgabe mit
setInterval
ausgeführt angezeigt. - Klicken Sie dann 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 ausgeführten Schleife werden fünf Artikel verarbeitet. Nachdem jedes Element verarbeitet wurde, wird setTimeout
zurückgegeben.
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 bestimmte Aufgaben in einem bestimmten Intervall ausführt. 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 erwü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 leicht abgeben möchten. Ertrag ist gut, da Nutzerinteraktionen früher ausgeführt werden können, aber auch andere Interaktionen, die nicht mit dem Nutzer zusammenhängen, Zeit für den Hauptthread erhalten. Es ist ein echtes Problem, aber scheduler.yield
kann 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 die Arbeit, die Sie sofort nach dem Aufgeben fortsetzen möchten, nicht für Aufgaben aus anderen Quellen übernommen wird (mit 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 scheduler.yield
in Aktion:
- 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, die mit setTimeout
liefert, können Sie sehen, dass die Schleife – obwohl sie nach jeder Iteration liefert – die verbleibende Arbeit nicht in den hinteren Teil der Warteschlange, sondern an den Anfang verschiebt. 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 Sie scheduler.yield
interessieren und 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 Bereich Experimentelle Webplattformfunktionen im Drop-down-Menü die Option Aktivieren aus. Dadurch werdenscheduler.yield
und alle anderen experimentellen Funktionen nur in Ihrer Instanz von Chrome 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 für einen bestimmten Zeitraum bedenkenlos testen und das Chrome-Team wertvolle Einblicke in die Verwendung dieser Funktionen in der Praxis gewinnen. Weitere Informationen zur Funktionsweise von Ursprungstests 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 zur 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.