Jetzt neu: Ursprungstest „scheduler.yield“

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">
</ph> Eine Darstellung, wie das Aufteilen einer Aufgabe zu einer besseren Reaktionsfähigkeit bei Eingaben führen kann. Oben wird durch eine lange Aufgabe die Ausführung eines Event-Handlers blockiert, bis die Aufgabe abgeschlossen ist. Im unteren Bereich ermöglicht die aufgeteilte Aufgabe, dass der Ereignis-Handler früher ausgeführt werden kann, als dies sonst der Fall wäre. <ph type="x-smartling-placeholder">
</ph> Eine Visualisierung der Steuerung des Hauptthreads. Oben werden Ergebnisse erst erzielt, nachdem eine Aufgabe vollständig ausgeführt wurde. Dies bedeutet, dass die Ausführung von Aufgaben länger dauern kann, bevor die Steuerung zurück an den Hauptthread zurückgegeben wird. Im unteren Bereich wird explizit nachgestellt. Dabei wird eine lange Aufgabe in mehrere kleinere Aufgaben unterteilt. So können Nutzerinteraktionen früher ausgeführt werden, was die Reaktionsfähigkeit von Eingaben und den INP verbessert.

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 der 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 Erzeugung von 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:

  1. 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 den Status Ran Blocking Task with setInterval lauten.
  2. 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 verdeutlicht 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 Informationen zum Hauptthread führt und beim Aufruf ein Promise zurückgibt. Das bedeutet, dass Sie sie mit await in einer async-Funktion ausführen können:

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

So sehen Sie scheduler.yield in Aktion:

  1. Rufen Sie chrome://flags auf.
  2. Aktivieren Sie den Test Experimentelle Webplattform-Funktionen. Möglicherweise müssen Sie Chrome danach neu starten.
  3. Rufe die Demoseite auf oder verwende die eingebettete Version davon unter dieser Liste.
  4. Klicken Sie auf die obere Schaltfläche Tasks regelmäßig ausführen.
  5. 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 Eingabereaktion 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:

  1. Wenn Sie scheduler.yield lokal testen möchten, geben Sie chrome://flags in die Adressleiste von Chrome ein und wählen Sie Aktivieren aus dem Drop-down-Menü im Bereich Experimentelle Webplattform-Funktionen aus. Dadurch werden scheduler.yield und alle anderen experimentellen Funktionen nur in Ihrer Instanz von Chrome verfügbar.
  2. 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ür scheduler.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:

  1. Sie verwenden scheduler.postTask bereits in Ihrer Anwendung, um Aufgaben zu planen.
  2. Sie möchten in der Lage sein, Aufgaben festzulegen und Prioritäten zu setzen.
  3. Sie möchten Aufgaben über die TaskController-Klasse, die die scheduler.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