Zum Inhalt springen
Core Web Vitals & Messung

Lange Tasks aufteilen: INP mit scheduler.yield senken

Lange JavaScript-Tasks über 50 ms mit scheduler.yield() in Häppchen zerlegen, den Hauptthread zwischen den Chunks freigeben und den INP unter 200 ms halten.

12 Min. Lesezeit INPJavaScriptMain Threadscheduler.yieldCore Web Vitals

Ein Klick, der erst nach einer gefühlten Ewigkeit reagiert, ist einer der häufigsten Frustmomente im Web – und fast immer steckt dieselbe Ursache dahinter: Der Hauptthread des Browsers ist mit einer langen JavaScript-Aufgabe beschäftigt und kommt nicht dazu, auf die Eingabe zu reagieren. Genau das misst der Core Web Vital INP, die Interaction to Next Paint. Solange ein einzelner Task läuft, kann der Browser weder den Klick verarbeiten noch das nächste Bild zeichnen; die Seite wirkt eingefroren. Die Lösung klingt einfach und ist es im Kern auch: lange Aufgaben in kleine Häppchen zerlegen und den Hauptthread zwischen den Häppchen kurz freigeben, damit wartende Klicks sofort drankommen. Lange war dieses Freigeben umständlich und mit Nebenwirkungen behaftet. Mit scheduler.yield() steht seit Chrome 129 (Chrome for Developers) eine Funktion bereit, die genau dafür gemacht ist – und die 2026 in immer mehr Browsern verfügbar ist. Dieser Beitrag zeigt, wann eine Aufgabe zum Problem wird, wie scheduler.yield() den Hauptthread freigibt, warum es besser funktioniert als der alte setTimeout-Trick und wie Sie den Effekt in Ihren Felddaten nachweisen. In unserer Core-Web-Vitals-Optimierung ist das Zerlegen langer Tasks einer der wirksamsten Hebel für einen guten INP-Wert.

Das Wichtigste in Kürze

  • Der Browser erledigt fast alles auf einem einzigen Hauptthread. Jede Aufgabe ab 50 Millisekunden (web.dev) gilt als Long Task; solange sie läuft, bleiben Eingaben liegen, und bei 60 Hertz bleiben pro Bild nur rund 16 Millisekunden (web.dev).
  • Als gut gilt ein INP unter 200 Millisekunden (web.dev), ausgewertet am 75. Perzentil (web.dev). Seit März 2024 (web.dev) ist INP ein Core Web Vital und misst anders als der frühere First Input Delay die gesamte Zeit bis zum sichtbaren Ergebnis.
  • scheduler.yield() gibt den Hauptthread frei und setzt die eigene Arbeit danach bevorzugt fort, weil die Fortsetzung vorn in die Warteschlange kommt. Verfügbar ist die Funktion in Chromium-basierten Browsern seit Chrome 129 (Chrome for Developers).
  • Der alte setTimeout-Weg reiht die Fortsetzung ans Ende der Warteschlange und unterliegt nach 5 verschachtelten Timern einer Mindestverzögerung von 4 Millisekunden (MDN). Als Fallback taugt er, als Standardweg nicht mehr.
  • Nicht nach jeder Iteration übergeben: Sinnvoll ist ein Zeitbudget von rund 50 Millisekunden (web.dev), ergänzt um navigator.scheduling.isInputPending(). Geprüft wird die Total Blocking Time im Labor und der INP in den Felddaten.

Warum lange Tasks die Reaktion blockieren

Der Browser erledigt fast alles auf einem einzigen Hauptthread: Er wertet JavaScript aus, berechnet das Layout, zeichnet Bilder und verarbeitet Eingaben wie Klicks, Tastendrücke und Scrollbewegungen. Weil dieser Thread nur eine Sache zur Zeit tun kann, gilt: Solange er mit einer langen Berechnung beschäftigt ist, bleibt jede Eingabe liegen. Das Chrome-Team zählt jede Aufgabe ab 50 Millisekunden (web.dev) als Long Task, weil ab dieser Dauer die Gefahr steigt, dass eine Interaktion nicht rechtzeitig beantwortet wird. Das RAIL-Modell empfiehlt, auf Eingaben innerhalb von 50 Millisekunden (web.dev) zu reagieren, damit sich die Reaktion unmittelbar anfühlt. Zum Vergleich: Bei einem Bildschirm mit 60 Hertz bleiben pro Bild nur rund 16 Millisekunden (web.dev) Zeit – ein Task von mehreren Hundert Millisekunden verschluckt also dutzende Frames am Stück und lässt die Seite ruckeln.

Der INP fasst diese Erfahrung in einer Zahl zusammen. Er beobachtet über den gesamten Seitenbesuch hinweg alle Klicks, Tastatureingaben und Tap-Gesten und meldet am Ende annähernd die längste beobachtete Verzögerung – ausgewertet am 75. Perzentil (web.dev) der Interaktionen, damit einzelne Ausreißer den Wert nicht verfälschen. Als gut gilt ein INP unter 200 Millisekunden (web.dev); zwischen 200 und 500 Millisekunden (web.dev) besteht Handlungsbedarf, und über 500 Millisekunden (web.dev) ist der Wert schlecht. Seit März 2024 (web.dev) ist INP einer der drei Core Web Vitals und hat den alten First Input Delay abgelöst, dessen Schwelle noch bei 100 Millisekunden (web.dev) lag. Der Unterschied ist entscheidend: FID maß nur die Verzögerung bis zum Beginn der Verarbeitung, INP dagegen die gesamte Zeit bis zum sichtbaren Ergebnis – und deckt lange Tasks damit gnadenlos auf.

Was genau ist ein Long Task?

Ein Long Task ist jede zusammenhängende Aufgabe auf dem Hauptthread, die länger als 50 Millisekunden (web.dev) dauert. In dieser Zeit ist der Thread blockiert: Klicks, Tastendrücke und Animationen müssen warten. Die Laborkennzahl Total Blocking Time summiert für jeden Long Task die Zeit oberhalb der 50-Millisekunden-Marke (web.dev) auf und ist damit ein guter Indikator dafür, wie reaktionsfreudig eine Seite später im Feld sein wird. Wer die Total Blocking Time senkt, verbessert erfahrungsgemäß auch den INP.

Was scheduler.yield anders macht als setTimeout

Die Idee, den Hauptthread zwischenzeitlich freizugeben, ist nicht neu. Klassisch behalf man sich mit setTimeout und null Millisekunden Verzögerung: Man unterbrach die Arbeit, reihte den Rest über einen Timer neu ein und gab dem Browser so die Chance, dazwischen Eingaben zu verarbeiten. Der Trick funktioniert, hat aber zwei Haken. Erstens landet der fortgesetzte Teil am Ende der Aufgaben-Warteschlange – hinter allem, was der Browser sonst noch eingeplant hat, inklusive fremder Skripte. Zweitens erzwingt die HTML-Spezifikation nach 5 verschachtelten Timern eine Mindestverzögerung von 4 Millisekunden (MDN), die sich bei vielen Häppchen aufsummiert. Das Ergebnis: Die Arbeit gibt zwar nach, wird danach aber möglicherweise lange nicht fortgesetzt.

scheduler.yield() löst genau dieses Problem. Der Aufruf gibt den Hauptthread ebenfalls frei, sodass der Browser wartende Klicks und ausstehende Renderarbeit erledigen kann – doch anschließend setzt er die eigene Aufgabe bevorzugt fort. Die Fortsetzung wird nicht ans Ende, sondern an den Anfang der Warteschlange gestellt, sodass die begonnene Arbeit nicht von neu eingetroffenen Aufgaben überholt wird. Man bekommt so das Beste aus beiden Welten: Der Browser bleibt reaktionsfreudig, und die Berechnung läuft trotzdem zügig zu Ende. scheduler.yield() gibt ein Promise zurück und wird mit await verwendet – der Code liest sich dadurch fast wie eine gewöhnliche Schleife. Verfügbar ist die Funktion in Chromium-basierten Browsern seit Chrome 129 (Chrome for Developers), das im September 2024 (Chrome for Developers) erschien, und sie wird 2026 zunehmend breiter unterstützt.

yield-to-main.js
// Übergabe an den Hauptthread, mit Fallback für ältere Browser
async function yieldToMain() {
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }
  // Fallback: Fortsetzung ans Ende der Aufgaben-Warteschlange
  return new Promise((resolve) => setTimeout(resolve, 0));
}

Damit die Technik auch in Browsern greift, die scheduler.yield() noch nicht kennen, kapselt man den Aufruf in eine kleine Hilfsfunktion mit Feature-Erkennung. Ist die Funktion vorhanden, wird sie genutzt; andernfalls fällt der Code sauber auf den bewährten setTimeout-Weg zurück. So profitieren moderne Browser vom bevorzugten Fortsetzen, während ältere zumindest die grundlegende Freigabe erhalten – ohne dass die Anwendung an einer fehlenden Funktion scheitert.

Eine lange Schleife in Häppchen zerlegen

Der häufigste Auslöser für lange Tasks ist eine Schleife, die über viele Elemente iteriert und für jedes eine nicht ganz billige Operation ausführt – etwa das Aufbereiten einer großen Produktliste, das Verarbeiten einer umfangreichen JSON-Antwort oder das Erzeugen vieler DOM-Knoten. Führt der Code die gesamte Schleife am Stück aus, entsteht ein einziger langer Task. Die Lösung besteht darin, nach jedem Arbeitsschritt – oder nach einer Gruppe von Schritten – kurz an den Hauptthread zu übergeben. Im einfachsten Fall genügt ein await auf die Hilfsfunktion mitten in der Schleife.

process-entries.js
async function processEntries(entries) {
  for (const entry of entries) {
    processEntry(entry);   // teure Einzelarbeit
    await yieldToMain();   // Hauptthread freigeben
  }
}

Nicht nach jeder Iteration yielden

So verlockend es ist, nach jedem einzelnen Schleifendurchlauf zu übergeben – es kann die Gesamtdauer spürbar verlängern. Jede Übergabe kostet einen kleinen Overhead, und wenn die Einzelarbeit ohnehin nur eine Millisekunde dauert, überwiegt dieser Overhead schnell den Nutzen. Sinnvoller ist ein Zeitbudget: Der Code arbeitet so lange am Stück, bis er rund 50 Millisekunden (web.dev) verbraucht hat, und gibt erst dann frei. So bleibt jeder Chunk unter der Long-Task-Grenze, ohne dass unnötig oft unterbrochen wird.

Nur yielden, wenn Input wartet

Noch feiner lässt sich steuern, indem man nur dann übergibt, wenn tatsächlich eine Eingabe wartet. Die Browser-Funktion navigator.scheduling.isInputPending() verrät, ob ein Klick oder Tastendruck in der Warteschlange liegt. Kombiniert man sie mit einem Zeitbudget, entsteht ein Muster, das den Hauptthread nur dann freigibt, wenn es sich lohnt: entweder weil das Budget erschöpft ist oder weil ein Nutzer gerade interagiert. In ruhigen Phasen läuft die Arbeit ohne Unterbrechung durch, in hektischen wird sofort reagiert. Der Fragezeichen-Operator hält den Code dabei robust, falls ein Browser die Funktion nicht anbietet.

in-chunks.js
async function inChunks(entries) {
  let lastYield = performance.now();
  for (const entry of entries) {
    processEntry(entry);
    // Nur yielden, wenn 50 ms verbraucht sind oder Eingabe wartet
    if (performance.now() - lastYield > 50 ||
        navigator.scheduling?.isInputPending?.()) {
      await yieldToMain();
      lastYield = performance.now();
    }
  }
}

Große Datenmengen aufbereiten

Filtern, Sortieren oder Umrechnen langer Listen im Browser ist ein Klassiker unter den langen Tasks. Ein Zeitbudget mit Übergabe hält die Oberfläche bedienbar, während im Hintergrund gerechnet wird.

Viel DOM auf einmal bauen

Wird eine umfangreiche Tabelle oder Liste in einem Rutsch erzeugt, blockiert das Einfügen den Thread. In Häppchen aufgeteilt erscheint der Inhalt gestaffelt, aber die Seite bleibt reaktionsfähig.

Beim Laden viel initialisieren

Init-Skripte, Hydration und nachgeladene Bausteine erledigen oft viel auf einmal. Die Arbeit über mehrere Chunks zu verteilen, verhindert einen langen Block direkt nach dem ersten Rendern.

Alternativen und der Fallback

scheduler.yield() ist nicht das einzige Werkzeug zur Aufgabenplanung. Die verwandte Funktion scheduler.postTask() plant ganze Aufgaben mit einer von 3 Prioritätsstufen (MDN) ein – user-blocking, user-visible und background – und eignet sich, um Arbeit von vornherein nach Wichtigkeit zu staffeln. requestIdleCallback wiederum schiebt unkritische Arbeit in die Leerlaufzeiten des Browsers. Für das reine Aufteilen einer langen, zusammenhängenden Aufgabe ist scheduler.yield() aber die direkteste Wahl, weil es die begonnene Arbeit bevorzugt fortsetzt. In Browsern ohne Unterstützung bleibt der setTimeout-Fallback – er gibt zwar nur ans Ende der Warteschlange zurück, verhindert aber immerhin den langen Block.

VerfahrenGibt Hauptthread freiFortsetzungBester Einsatz
setTimeout(fn, 0)enthalten Ende der Queue, Clamp ab 4 msFallback für ältere Browser
scheduler.postTask()Plant ganze AufgabenNach PrioritätsstufeArbeit nach Wichtigkeit staffeln
requestIdleCallback()In LeerlaufzeitenWenn Zeit übrig istUnkritische Hintergrundarbeit
scheduler.yield()enthalten Bevorzugt, vorne in der QueueLange Aufgabe in Häppchen teilen

Der Kern in einem Satz

Nicht die schnellste Berechnung macht eine Seite reaktionsfreudig, sondern die richtige Unterbrechung: eine lange Aufgabe in Häppchen unter 50 Millisekunden zerlegen und dazwischen mit scheduler.yield() den Hauptthread freigeben – so kommt der Klick vor der Fertigstellung dran.

So messen Sie den Effekt

Ob das Aufteilen wirkt, lässt sich mit frei verfügbaren Werkzeugen prüfen. Im Labor zeigt der Lighthouse-Bericht die Total Blocking Time und markiert lange Tasks; sinkt die Zahl der Blöcke über 50 Millisekunden (web.dev), war die Aufteilung erfolgreich. Das Performance-Panel der Chrome-Entwicklerwerkzeuge stellt jeden Task als Balken dar – lange Aufgaben werden markiert und lassen sich vor und nach der Änderung direkt vergleichen. Für die Ursachensuche in echten Sitzungen liefert die Long Animation Frames API eine genaue Zuordnung, welches Skript einen Frame aufgehalten hat; wie sich damit INP-Bremsen im Frame aufspüren lassen, behandeln wir gesondert. Die quelloffene web-vitals-Bibliothek meldet den INP zusätzlich aus den Felddaten echter Nutzer.

  1. Lange Tasks im Lighthouse-Bericht oder im Performance-Panel identifizieren
  2. Die auslösende Funktion bestimmen – meist eine Schleife oder ein Init-Block
  3. Eine yieldToMain-Hilfsfunktion mit Feature-Erkennung und Fallback anlegen
  4. Die Arbeit mit einem Zeitbudget von rund 50 Millisekunden in Häppchen teilen
  5. Nur bei erschöpftem Budget oder wartender Eingabe an den Hauptthread übergeben
  6. Vorher und nachher messen – Total Blocking Time im Labor, INP in den Felddaten

Wichtig ist, im Labor mit realistischer Drosselung zu messen: Ein schneller Bürorechner verbirgt genau die langen Tasks, die auf einem durchschnittlichen Smartphone zum Problem werden. Deshalb gehört zu jeder Optimierung der Blick ins Feld – nur dort zeigt sich, was echte Nutzer auf ihren Geräten erleben. Und weil sich neue lange Tasks leicht wieder einschleichen, etwa durch ein zusätzliches Skript oder eine gewachsene Datenmenge, lohnt es sich, die Werte dauerhaft zu überwachen und Regressionen früh zu stoppen, wie wir es beim Performance-Budget im CI beschreiben.

Vom Task-Splitting zur stabilen INP

Das Aufteilen langer Tasks ist selten die einzige Maßnahme, aber oft die wirkungsvollste für die Reaktionsschnelligkeit. Häufig stammen die längsten Aufgaben nicht einmal aus dem eigenen Code, sondern aus eingebundenen Fremdskripten – Werbe-, Analyse- oder Chat-Bausteinen, die beim Laden viel auf einmal erledigen. Wie sich solche Drittanbieter-Skripte entschlacken lassen, ist deshalb der natürliche zweite Schritt. Ergänzend hilft ein bewusstes JavaScript-Performance-Budget, damit gar nicht erst zu viel Code auf einmal läuft. Und wer den INP im Zusammenhang mit den beiden anderen Kennzahlen verstehen will, findet die Einordnung im Beitrag zu den Core Web Vitals 2026 sowie im Überblick zum INP gezielt optimieren.

Reaktionsschnelligkeit entsteht nicht dadurch, dass wir alles schneller rechnen, sondern dass wir den Browser im richtigen Moment atmen lassen. Ein langer Task, klug in Häppchen zerlegt, fühlt sich für den Nutzer sofort flüssiger an – und der INP folgt dieser Erfahrung.

Projekterfahrung aus 50+ Performance-Projekten

Genau so gehen wir in einem strukturierten Performance-Audit vor: lange Tasks aufspüren, die auslösenden Funktionen benennen und sie mit scheduler.yield() und einem sauberen Fallback in reaktionsfreundliche Häppchen zerlegen – anschließend prüfen wir in Ihren Felddaten, ob der INP dauerhaft im grünen Bereich bleibt. Welche Leistungen dazugehören, zeigt unsere Übersicht der Performance-Leistungen; die konkreten Frontend-Hebel bündeln wir in der Frontend-Optimierung.

Dieser Artikel basiert auf Daten aus: Chrome for Developers (Use scheduler.yield() to break up long tasks) und web.dev (Optimize INP, top-cwv, Total Blocking Time, RAIL) sowie MDN Web Docs (Scheduler.yield, Scheduler.postTask, setTimeout). Alle genannten Werte wurden zum Zeitpunkt der Veröffentlichung geprüft.

Verwandte Artikel

Grundlagen & Strategie

JavaScript und Performance Budgets: Weniger Code

JavaScript-Einfluss auf INP und Ladezeit minimieren: Performance Budgets, Code Splitting, Tree Shaking und Third-Party-Management für Core Web Vitals.

14 Min. Lesezeit
Frontend-Optimierung

Speicherlecks im Frontend: wenn die Seite träge wird

Die Oberfläche startet schnell und wird nach zwanzig Minuten zäh: wie Speicherlecks im Frontend entstehen, wie man sie im Verlauf misst und wieder loswird.

12 Min. Lesezeit
Core Web Vitals & Messung

Web Worker: Main-Thread entlasten und INP verbessern

Rechenintensives JavaScript in einen Web Worker auslagern hält den Main-Thread frei, senkt die Total Blocking Time und bringt INP unter die 200-ms-Schwelle.

12 Min. Lesezeit