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 bekannt gegeben, dass der INP-Messwert (Interaction to Next Paint) von „Experimental“ zu „Ausstehend“ wechseln wird. First Input Delay (FID) wird im März 2024 als Core Web Vital ersetzt.
Wir arbeiten kontinuierlich an neuen APIs, mit denen Webentwickler ihre Websites noch übersichtlicher gestalten können. Deshalb führt das Chrome-Team derzeit einen Ursprungstest für scheduler.yield
ab Version 115 von Chrome durch. scheduler.yield
ist eine vorgeschlagene neue Ergänzung der Planer-API, die sowohl eine einfachere als auch bessere Möglichkeit zur Steuerung des Hauptthreads bietet als die bisher genutzten Methoden.
Beim Ertrag
JavaScript verwendet das Run-to-Completion-Modell für die Verarbeitung von Aufgaben. Dies bedeutet, dass eine Aufgabe, die im Hauptthread ausgeführt wird, so lange ausgeführt wird, wie es für den Abschluss erforderlich ist. Nach Abschluss einer Aufgabe wird die Steuerung yield 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 beendet wird, z. B. bei einer Endlosschleife, ist das Ausweichen ein unvermeidlicher Aspekt der Aufgabenplanungslogik von JavaScript. Das wird passieren, es ist nur eine Frage des Wann-Zeitraums – und früher ist besser als später. Wenn die Ausführung von Aufgaben zu lange dauert – mehr als 50 Millisekunden, um genau zu sein – werden sie als lange Aufgaben betrachtet.
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 ausgeführt werden, desto wahrscheinlicher ist es, dass Nutzer den Eindruck haben, dass die Seite träge oder gar nicht funktioniert.
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 Reaktionsfähigkeit auf Nutzereingaben auf einer Seite verbessern, indem Sie explizit in einer Aufgabe nachgeben. Dadurch wird die Aufgabe aufgeteilt und bei der nächsten verfügbaren Gelegenheit abgeschlossen. Auf diese Weise erhalten andere Aufgaben schneller Zeit für den Hauptthread, als wenn sie auf die Fertigstellung langer Aufgaben warten müssten.
<ph type="x-smartling-placeholder">Wenn Sie explizit nachgeben, sagen Sie dem Browser, dass meine Arbeit eine Weile dauern kann und Sie nicht alle Aufgaben erledigen müssen, bevor Sie auf Nutzereingaben oder andere wichtige Aufgaben reagieren können. Es ist ein wertvolles Tool in der Entwicklungsumgebung, mit dem Sie die Nutzererfahrung erheblich verbessern können.
Das Problem mit aktuellen Ertragsstrategien
Eine gängige Methode zur Erzeugung verwendet setTimeout
mit einem Zeitlimit von 0
. Dies funktioniert, weil der an setTimeout
übergebene Callback die verbleibende Arbeit in eine separate Aufgabe verschiebt, die zur nachfolgenden 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, stehen weiterhin am Anfang der Warteschlange – aber die verbleibende Arbeit, die Sie nach expliziter Erbringung erledigen wollten, kann am Ende weiter durch andere Aufgaben aus konkurrierenden Quellen verzögert werden, die vor der Warteschlange in die Warteschlange gestellt wurden.
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, das protokolliert, wenn Aufgaben ausgeführt werden. Auf dieser Seite haben Sie folgende Möglichkeiten:
- 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, wird das Aufgabenprotokoll mit mehreren Meldungen gefüllt, die Ran Blocking Task with
setInterval
lauten. - Klicken Sie dann auf die Schaltfläche Schleife ausführen, wobei bei jeder Iteration
setTimeout
zurückgegeben wird.
Im Feld unten in der Demo steht in 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 „Ende der Aufgabenwarteschlange“ die bei der Erzeugung von setTimeout
auftritt. In der ausgeführten Schleife werden fünf Artikel verarbeitet. Nachdem jedes Element verarbeitet wurde, wird setTimeout
zurückgegeben.
Dies veranschaulicht ein häufiges Problem im Web: Es ist nicht ungewöhnlich, dass ein Skript – insbesondere ein Drittanbieter-Skript – eine Timer-Funktion registriert, die in einem bestimmten Intervall ausgeführt wird. Das „Ende der Aufgabenwarteschlange“ Verhalten, das mit setTimeout
einhergeht, bedeutet, dass Arbeit aus anderen Aufgabenquellen möglicherweise vor der verbleibenden Arbeit in die Warteschlange gestellt wird, die die Schleife nach dem Ausgeben erledigen muss.
Je nach Anwendung kann dies ein gewünschtes Ergebnis sein oder nicht. In vielen Fällen ist dieses Verhalten jedoch der Grund, warum Entwickler sich zögern fühlen, die Kontrolle über den Hauptthread so leicht aufzugeben. 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
steht seit Chrome-Version 115 hinter einer Kennzeichnung als experimentelle Webplattformfunktion zur Verfügung. Eine Frage, die Sie sich vielleicht stellen möchten, lautet: „Warum benötige ich zum Ertrag eine spezielle Funktion, wenn setTimeout
das bereits erledigt?“
Erwähnenswert war, dass die Erzeugung kein Designziel von setTimeout
war, sondern ein schöner Nebeneffekt bei der Planung eines Callbacks, der zu einem späteren Zeitpunkt ausgeführt wird – selbst bei einem Zeitlimit von 0
. Wichtig ist jedoch, sich zu vergegenwärtigen, dass bei einem Ergebnis mit setTimeout
verbleibende Arbeit an den Back-End 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 mit dem Hauptthread verbunden ist und beim Aufruf ein Promise
zurückgibt. Das bedeutet, dass Sie sie mit await
in einer async
-Funktion verwenden 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://flags
auf. - Aktivieren Sie den Test Experimentelle Webplattform-Funktionen. Möglicherweise müssen Sie Chrome danach neu starten.
- Rufe die Demoseite auf oder verwende die eingebettete Version davon unter dieser Liste.
- Klicken Sie auf die obere Schaltfläche Tasks regelmäßig ausführen.
- Klicken Sie abschließend auf die Schaltfläche Schleife ausführen, wobei bei jeder Iteration
scheduler.yield
zurückgegeben wird.
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 erhalten Sie das Beste aus beiden Welten: Sie können etwas nachlassen, um die Reaktionsgeschwindigkeit von Eingabedaten auf Ihrer Website zu verbessern, aber auch dafür zu sorgen, dass die Arbeit, die Sie nach der Ausbeutung erledigen wollten, nicht verzögert wird.
Probier die Funktion einfach 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 testen möchten, geben Siechrome://flags
in die Adressleiste von Chrome ein und wählen Sie Aktivieren aus dem Drop-down-Menü im Bereich Experimentelle Webplattform-Funktionen 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 mit einem öffentlich zugänglichen Ursprung aktivieren möchten, müssen Sie sich für den Ursprungstest fürscheduler.yield
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 und gleichzeitig Browser unterstützen, die diese Funktion nicht implementieren – hängt von Ihren Zielen ab. Du kannst den offiziellen Polyfill verwenden. Polyfill ist nützlich, wenn Folgendes auf deine Situation zutrifft:
- Sie verwenden
scheduler.postTask
bereits in Ihrer Anwendung, um Aufgaben zu planen. - Sie möchten in der Lage sein, Aufgaben festzulegen und Prioritäten zu setzen.
- Sie möchten Aufgaben über die
TaskController
-Klasse, die diescheduler.postTask
API anbietet, abbrechen oder ihnen neue Prioritäten zuweisen.
Wenn dies nicht auf deine Situation zutrifft, ist Polyfill möglicherweise nicht für dich geeignet. In diesem Fall haben Sie mehrere Möglichkeiten, einen Roll-out für Ihren eigenen Fallback durchzuführen. Bei der ersten Methode wird scheduler.yield
verwendet, wenn sie verfügbar ist. Ist dies nicht der Fall, 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 vielleicht denken, gibt es in Browsern, die scheduler.yield
nicht unterstützen, ohne "Vor der Warteschlange". verhalten. Wenn das bedeutet, dass Sie überhaupt keinen Ertrag erzielen möchten, können Sie einen anderen Ansatz ausprobieren, bei dem scheduler.yield
verwendet wird, wenn das Produkt verfügbar ist. Wenn es nicht verfügbar ist, wird überhaupt kein Ertrag erzielt:
// 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 Planer-API, mit der Entwickler hoffentlich leichter die Reaktionsfähigkeit verbessern können als aktuelle Ertragsstrategien. Wenn die API scheduler.yield
für Sie nützlich erscheint, nehmen Sie bitte an unserer Umfrage teil, damit wir sie verbessern können, und geben Sie uns Feedback, damit wir sie noch besser machen können.
Hero-Image von Unsplash von Jonathan Allison