Start · Sprachen · JavaScript · Referenz · Scheduling

Scheduling

Schnittstelle

Über <code>navigator.scheduling</code> erreichbares Objekt, mit dem Skripte abfragen können, ob Benutzereingaben warten, und so lange Aufgaben rechtzeitig unterbrechen können.

seit JavaScript Experimentell: Chromium 87+ (Chrome, Edge, Opera); Kategorie: webapi

Signatur

interface Scheduling

Beschreibung

Die Schnittstelle Scheduling gehört zur experimentellen Prioritized Task Scheduling- bzw. isInputPending-Erweiterung. Man greift auf eine Instanz nicht per Konstruktor zu, sondern über navigator.scheduling. Ihre einzige Methode ist derzeit isInputPending().

navigator.scheduling.isInputPending() gibt true zurück, wenn in der Ereigniswarteschlange Eingaben wie Klicks, Tastendrücke oder Touch-Ereignisse auf Verarbeitung warten. So kann ein langer, in Teilstücke aufgeteilter Rechenvorgang nur dann an den Browser abgeben, wenn wirklich etwas ansteht. Man muss also nicht nach einem festen Zeitplan, zum Beispiel alle 50 ms, unterbrechen. Das hält die Seite reaktionsfähig und verbessert Kennzahlen wie Interaction to Next Paint (INP), ohne unnötig Durchsatz zu verlieren.

Typische Einsatzfälle sind das Verarbeiten großer Datenmengen im Hauptthread, das schrittweise Aufbauen umfangreicher DOM-Strukturen und Hydration-Schritte in Frameworks. Die Methode bricht selbst nichts ab. Sie liefert nur einen Hinweis. Das eigentliche Abgeben an den Browser muss man selbst erledigen, etwa mit setTimeout(), MessageChannel oder scheduler.yield().

Parameter

Name Typ Default Beschreibung
$options object {} Optionales Einstellungsobjekt für isInputPending(options).
$options.includeContinuous boolean false Bei true zählen auch fortlaufende Ereignisse wie mousemove, pointermove oder wheel als wartende Eingabe. Bei false werden nur einzelne Ereignisse wie click oder keydown berücksichtigt.

Rückgabewert

Typ
boolean
Beschreibung
isInputPending() gibt true zurück, wenn Benutzereingaben auf Verarbeitung warten, sonst false. Ist die API nicht verfügbar, ist navigator.scheduling undefined.

Beispiele

Verfügbarkeit prüfen

const hasInputPending =
  'scheduling' in navigator &&
  typeof navigator.scheduling.isInputPending === 'function';

console.log(hasInputPending ? 'isInputPending wird unterstützt' : 'Nicht unterstützt');
isInputPending wird unterstützt // in Chromium-Browsern

Lange Aufgabe nur bei wartender Eingabe unterbrechen

const yieldToMain = () => new Promise((resolve) => setTimeout(resolve, 0));

const processItems = async (items, handle) => {
  const DEADLINE_MS = 50;
  let start = performance.now();

  for (const item of items) {
    handle(item);

    const inputWaiting = navigator.scheduling?.isInputPending?.() ?? false;
    const overBudget = performance.now() - start > DEADLINE_MS;

    if (inputWaiting || overBudget) {
      await yieldToMain();          // Browser verarbeitet Eingaben/Rendering
      start = performance.now();
    }
  }
};

const data = Array.from({ length: 100_000 }, (_, i) => i);
let sum = 0;
processItems(data, (n) => { sum += Math.sqrt(n); })
  .then(() => console.log('Fertig, Summe:', Math.round(sum)));
Fertig, Summe: 21081835

Fortlaufende Ereignisse (Mausbewegung) einbeziehen

const renderChunk = (list, rows, from, to) => {
  for (let i = from; i < to && i < rows.length; i++) {
    const li = document.createElement('li');
    li.textContent = rows[i];
    list.append(li);
  }
};

const renderAll = async (rows) => {
  const list = document.querySelector('#liste');
  let i = 0;
  while (i < rows.length) {
    renderChunk(list, rows, i, i + 200);
    i += 200;
    // Auch bei Scroll- oder Mausbewegungen abgeben
    if (navigator.scheduling?.isInputPending({ includeContinuous: true })) {
      await new Promise((r) => setTimeout(r, 0));
    }
  }
  console.log(`${rows.length} Zeilen gerendert`);
};

renderAll(Array.from({ length: 5000 }, (_, n) => `Eintrag ${n + 1}`));
5000 Zeilen gerendert

Moderne Variante mit scheduler.yield() und Rückfallebene

const yieldToMain = () =>
  globalThis.scheduler?.yield
    ? scheduler.yield()
    : new Promise((resolve) => setTimeout(resolve, 0));

const work = async (tasks) => {
  for (const task of tasks) {
    task();
    if (navigator.scheduling?.isInputPending?.() ?? true) {
      await yieldToMain();
    }
  }
  console.log('Alle Aufgaben erledigt');
};

work([() => 1 + 1, () => 'a'.repeat(10), () => [3, 1, 2].sort()]);
Alle Aufgaben erledigt

// Wichtig · Fallstricke

  • Experimentell und nur in Chromium verfügbar: Firefox und Safari unterstützen die API nicht. Prüfe deshalb immer mit navigator.scheduling?.isInputPending, ob sie vorhanden ist, und sieh eine Ersatzlösung vor, etwa einen reinen Zeitbudget-Ansatz.
  • Nur ein Hinweis, keine Garantie: Ein Rückgabewert von false heißt nicht, dass man nie abgeben muss. Auch Rendering, Animationen und Netzwerk-Callbacks brauchen den Hauptthread. Kombiniere die Abfrage daher mit einer Zeitgrenze von etwa 50 ms.
  • Eingaben in Cross-Origin-iframes werden aus Datenschutzgründen nicht immer erkannt. Das Ergebnis kann also false sein, obwohl der Nutzer gerade in einem eingebetteten Frame interagiert.
  • Das Chrome-Team empfiehlt inzwischen meist scheduler.yield() oder scheduler.postTask(), weil regelmäßiges Abgeben auch ohne isInputPending() gut funktioniert und breiter unterstützt wird.
  • Die Methode ist nur im Hauptthread des Fensters verfügbar, nicht in Web Workern.
  • Ein Aufruf nach jedem einzelnen, sehr kleinen Arbeitsschritt verursacht messbaren Mehraufwand. Frage besser nach einem Block von Einträgen ab.