Start · Sprachen · JavaScript · Referenz · Scheduler.yield()

Scheduler.yield()

Methode

Gibt die Kontrolle während einer lang laufenden Aufgabe an den Haupt-Thread ab und setzt die Arbeit danach als bevorzugte Fortsetzung (Continuation) fort.

seit JavaScript Chrome/Edge 129+, Firefox 142+; Safari: nicht unte Kategorie: webapi

Signatur

Scheduler.prototype.yield()

Beschreibung

scheduler.yield() gibt ein Promise zurück, das aufgelöst wird, nachdem der Browser Gelegenheit hatte, andere Arbeit zu erledigen, etwa Eingaben verarbeiten, rendern oder Ereignisse behandeln. Mit await scheduler.yield() teilst du eine lange Aufgabe in mehrere kurze auf. Dadurch bleibt die Seite reaktionsfähig, und Kennzahlen wie Interaction to Next Paint (INP) verbessern sich.

Der entscheidende Unterschied zu älteren Tricks wie await new Promise(r => setTimeout(r, 0)): Die Fortsetzung wird nicht ans Ende der Aufgabenwarteschlange gestellt. Sie läuft als priorisierte Fortsetzung vor anderen Aufgaben gleicher Priorität. Die Seite bleibt reaktionsfähig, und trotzdem kann dein Code nicht von beliebigen anderen Skripten (z. B. Werbe- oder Tracking-Code) „überholt“ werden.

Ruft man yield() innerhalb einer mit scheduler.postTask() geplanten Aufgabe auf, übernimmt die Fortsetzung deren Priorität ("user-blocking", "user-visible" oder "background") und deren AbortSignal. Außerhalb von postTask(), etwa in einem Ereignis-Handler, gilt die Priorität "user-visible".

Einsatzgebiete sind Schleifen über große Datenmengen, aufwendige Berechnungen im Haupt-Thread, schrittweises Rendern großer Listen und Ereignis-Handler, die erst schnell sichtbares Feedback geben und dann den Rest nachziehen sollen.

Rückgabewert

Typ
Promise<undefined>
Beschreibung
Ein Promise, das mit undefined aufgelöst wird, sobald die Fortsetzung an der Reihe ist. Wurde das übernommene AbortSignal (aus einem umgebenden postTask()) abgebrochen, wird es mit dem reason des Signals abgelehnt, standardmäßig eine DOMException namens "AbortError".

Beispiele

Lange Schleife in Häppchen aufteilen

const items = Array.from({ length: 50_000 }, (_, i) => i);

const processItem = (n) => {
  // simulierte teure Arbeit
  let x = 0;
  for (let i = 0; i < 1_000; i++) x += Math.sqrt(n + i);
  return x;
};

const processAll = async () => {
  let deadline = performance.now() + 50; // max. ca. 50 ms am Stück
  let sum = 0;
  for (const item of items) {
    sum += processItem(item);
    if (performance.now() >= deadline) {
      await scheduler.yield(); // Browser darf rendern und Eingaben verarbeiten
      deadline = performance.now() + 50;
    }
  }
  console.log('Fertig:', items.length, 'Einträge');
};

processAll();
Fertig: 50000 Einträge

Feature-Erkennung mit Fallback

const yieldToMain = () => {
  if (globalThis.scheduler?.yield) {
    return scheduler.yield();
  }
  // Fallback: Fortsetzung landet am Ende der Warteschlange
  return new Promise((resolve) => setTimeout(resolve, 0));
};

const tasks = [
  () => console.log('Validierung'),
  () => console.log('Speichern'),
  () => console.log('Analytics senden'),
];

const run = async () => {
  for (const task of tasks) {
    task();
    await yieldToMain();
  }
};

run();
Validierung Speichern Analytics senden

Ereignis-Handler: erst Feedback, dann Rest

const button = document.querySelector('#save');
const status = document.querySelector('#status');

button.addEventListener('click', async () => {
  // 1. Sofort sichtbares Feedback
  status.textContent = 'Wird gespeichert …';

  // 2. Abgeben, damit der Browser das Feedback zeichnen kann
  await scheduler.yield();

  // 3. Teure Folgearbeit
  const data = JSON.stringify({ zeit: Date.now(), inhalt: 'x'.repeat(1e6) });
  localStorage.setItem('entwurf', data);
  status.textContent = 'Gespeichert';
});

Priorität und Abbruch aus postTask() übernehmen

const controller = new TaskController({ priority: 'background' });

const job = scheduler.postTask(async () => {
  for (let i = 0; i < 5; i++) {
    console.log('Schritt', i);
    // Fortsetzung bleibt 'background' und hängt am selben Signal
    await scheduler.yield();
  }
}, { signal: controller.signal });

// Nach kurzer Zeit abbrechen
setTimeout(() => controller.abort(), 0);

job.catch((err) => console.log('Abgebrochen:', err.name));
Schritt 0 Abgebrochen: AbortError

// Wichtig · Fallstricke

  • Browser-Unterstützung: Chromium-Browser ab Version 129 und Firefox ab Version 142 unterstützen die Methode, Safari noch nicht. Prüfe deshalb immer mit 'scheduler' in globalThis && 'yield' in scheduler und sieh einen Fallback über setTimeout vor. Das Beispiel zum Abbruch benutzt außerdem postTask() und TaskController, die ebenfalls nicht überall verfügbar sind.
  • Keine Parameter: Frühe Entwürfe und Origin Trials kannten Optionen wie { priority, signal }. Die aktuelle Spezifikation übernimmt beides ausschließlich aus dem umgebenden postTask()-Kontext.
  • Nicht zu oft abgeben: Jedes yield() kostet etwas Verwaltungsaufwand. Gib zeitbasiert ab (z. B. alle 50 ms, das entspricht der Schwelle für „Long Tasks“) und nicht nach jedem einzelnen Element.
  • Zustand kann sich ändern: Zwischen await scheduler.yield() und der Fortsetzung können Nutzereingaben oder andere Skripte DOM und Daten verändert haben. Prüfe wichtige Annahmen nach dem Abgeben erneut.
  • Kein Ersatz für Worker: Die Arbeit läuft weiterhin im Haupt-Thread. Für wirklich rechenintensive Aufgaben ist ein Web Worker meist die bessere Wahl.
  • Die Methode gibt es im Window und in Workern über das globale Objekt scheduler. Aufgerufen wird sie immer als scheduler.yield(). Eine Instanz von Scheduler erzeugt man nicht selbst.