requestIdleCallback und die Scheduler API für nicht-dringende Arbeit
AI generated
60fps
ms
Web Performance
requestIdleCallback und die Scheduler API für nicht-dringende Arbeit
Wie Browser-Leerlaufzeit sinnvoll für nicht-kritische Aufgaben genutzt wird

Nicht jede JavaScript-Aufgabe muss sofort ausgeführt werden. requestIdleCallback und die neuere Scheduler API erlauben es, nicht-dringende Arbeit gezielt in Phasen zu verschieben, in denen der Haupt-Thread ohnehin nichts Wichtigeres zu tun hat, ohne dabei die Reaktionsfähigkeit der Seite auf Nutzerinteraktionen zu gefährden.

15 Min. Lesezeit requestIdleCallback Scheduler API

1. Browser-Leerlaufzeit für nicht-kritische Arbeit nutzen

Der Haupt-Thread eines Browsers ist ständig zwischen verschiedenen Aufgaben hin- und hergerissen: Rendering, Layout-Berechnung, Event-Handling und die Ausführung von JavaScript konkurrieren alle um dieselbe einzelne Ausführungsspur. Wird dieser Thread durch lang laufende, nicht-dringende Aufgaben blockiert, leidet direkt die wahrgenommene Interaktivität der Seite, da Klicks, Taps oder Tastatureingaben erst verzögert verarbeitet werden können.

Viele Aufgaben in typischen Web-Anwendungen sind jedoch gar nicht zeitkritisch: das Versenden von Analytics-Ereignissen, das Vorausladen von Ressourcen für wahrscheinliche nächste Interaktionen oder das Aktualisieren nicht sichtbarer UI-Bereiche müssen nicht in derselben Millisekunde geschehen, in der sie ausgelöst werden. Genau für solche Aufgaben wurde requestIdleCallback entwickelt: eine Browser-API, die Arbeit gezielt in Phasen verschiebt, in denen der Haupt-Thread ohnehin frei ist.

2. requestIdleCallback im Detail

Die Funktion requestIdleCallback nimmt einen Callback entgegen, der vom Browser aufgerufen wird, sobald in einem Frame ungenutzte Zeit vorhanden ist, typischerweise nachdem Rendering und Layout abgeschlossen sind, aber bevor der nächste Frame beginnt. Der Callback erhält ein IdleDeadline-Objekt, über das per timeRemaining() abgefragt werden kann, wie viel Zeit innerhalb des aktuellen Leerlauffensters noch zur Verfügung steht, bevor der Browser wieder zu dringenderen Aufgaben zurückkehren muss.

Wichtig ist, dass requestIdleCallback keine Garantie für eine bestimmte Ausführungszeit gibt. Ist der Browser durchgehend mit Rendering oder Nutzerinteraktionen beschäftigt, kann der Callback deutlich später ausgeführt werden als erwartet. Über den optionalen timeout-Parameter lässt sich jedoch eine maximale Wartezeit erzwingen, nach deren Ablauf der Callback auch dann ausgeführt wird, wenn kein echter Leerlauf vorliegt, allerdings dann potenziell auf Kosten der Interaktivität.


function processAnalyticsQueue(deadline) {
  while (deadline.timeRemaining() > 0 && analyticsQueue.length > 0) {
    const event = analyticsQueue.shift();
    sendAnalyticsEvent(event);
  }

  if (analyticsQueue.length > 0) {
    // Noch Arbeit uebrig: naechstes Leerlauffenster anfordern
    requestIdleCallback(processAnalyticsQueue, { timeout: 2000 });
  }
}

requestIdleCallback(processAnalyticsQueue, { timeout: 2000 });

3. Die Deadline und timeRemaining() richtig einsetzen

Der entscheidende Baustein für eine korrekte Nutzung von requestIdleCallback ist die konsequente Prüfung von deadline.timeRemaining() innerhalb jeder Schleifeniteration. Statt eine große Menge an Arbeit in einem einzigen Callback-Aufruf zu erledigen, sollte die Arbeit in kleine, einzeln abarbeitbare Einheiten zerlegt werden, sodass die Schleife jederzeit unterbrochen werden kann, sobald das verbleibende Zeitfenster erschöpft ist.

Wird diese Prüfung vernachlässigt und ein Callback führt trotzdem eine lang laufende, nicht unterbrechbare Operation aus, geht der eigentliche Vorteil der API vollständig verloren: Der Haupt-Thread wird dann genauso blockiert wie bei einer synchronen Ausführung außerhalb von requestIdleCallback, nur eben zu einem späteren, weniger vorhersehbaren Zeitpunkt. Die Verantwortung für eine echte Unterbrechbarkeit liegt also vollständig beim Entwickler, nicht bei der Browser-API selbst.

4. Typische Anwendungsfälle: Analytics, Prefetching, UI-Updates

Analytics-Ereignisse sind der klassische Anwendungsfall, da sie für den Nutzer keine sichtbare Auswirkung haben und eine Verzögerung von wenigen hundert Millisekunden vollkommen unproblematisch ist. Ebenso eignet sich Prefetching hervorragend für requestIdleCallback: Ressourcen für wahrscheinliche nächste Navigationsziele können im Hintergrund geladen werden, sobald der Browser tatsächlich Kapazität dafür hat, statt die Ladezeit der aktuellen Seite durch zusätzliche parallele Requests zu verlangsamen.

Ein dritter häufiger Anwendungsfall sind Aktualisierungen von UI-Bereichen, die aktuell nicht im sichtbaren Bereich liegen, etwa das Vorbereiten von Inhalten für einen Tab, den der Nutzer noch nicht geöffnet hat, oder das Aufräumen nicht mehr benötigter DOM-Knoten. In all diesen Fällen ist der gemeinsame Nenner, dass eine Verzögerung der Aufgabe keinerlei negativen Effekt auf die vom Nutzer wahrgenommene Reaktionsfähigkeit der Seite hat.

5. Grenzen von requestIdleCallback

Trotz seiner Nützlichkeit hat requestIdleCallback einige praktische Einschränkungen. Die API kennt nur eine einzige, undifferenzierte Prioritätsstufe: Entweder eine Aufgabe wird über requestIdleCallback eingereiht, oder sie wird es nicht. Es gibt keine Möglichkeit, innerhalb der eingereihten Aufgaben selbst wieder zu priorisieren, sodass alle Leerlaufaufgaben um dasselbe begrenzte Zeitfenster konkurrieren, unabhängig davon, wie wichtig sie im Vergleich zueinander tatsächlich sind.

Ausserdem lässt sich eine bereits eingereihte Aufgabe nicht ohne Weiteres wieder abbrechen oder in ihrer Priorität ändern, was in komplexeren Anwendungen mit vielen gleichzeitig konkurrierenden Hintergrundaufgaben schnell unübersichtlich wird. Diese Einschränkungen waren einer der zentralen Gründe, warum die Scheduler API als Nachfolger und Ergänzung entwickelt wurde, mit einem deutlich flexibleren Prioritätsmodell.

6. Die neue Scheduler-API im Überblick

Die Scheduler API, zugänglich über das globale scheduler-Objekt und dessen Methode postTask(), erweitert das Konzept von requestIdleCallback um ein explizites, mehrstufiges Prioritätsmodell. Statt einer einzigen undifferenzierten Leerlaufwarteschlange können Aufgaben gezielt mit einer von drei Prioritätsstufen eingereiht werden, was dem Browser deutlich präzisere Informationen darüber liefert, wie dringend eine Aufgabe im Vergleich zu anderen tatsächlich ist.

Zusätzlich unterstützt postTask() ein AbortSignal, über das eine bereits eingereihte, aber noch nicht ausgeführte Aufgabe sauber abgebrochen werden kann, was bei requestIdleCallback nur umständlich über cancelIdleCallback möglich war und dort keine vergleichbare Flexibilität bot. Die API gibt zudem ein Promise zurück, was die Integration in moderne, auf async/await basierende Codebasen erheblich vereinfacht.

7. Prioritätsstufen: user-blocking, user-visible, background

Die Scheduler API kennt drei Prioritätsstufen. user-blocking ist für Aufgaben gedacht, die eine direkte Nutzerinteraktion blockieren würden, wenn sie nicht zeitnah ausgeführt werden, etwa das Verarbeiten einer Formulareingabe. user-visible, die Standardpriorität, eignet sich für Aufgaben, die zwar sichtbare Auswirkungen haben, aber nicht sofort erfolgen müssen, etwa das Aktualisieren einer sichtbaren, aber nicht kritischen UI-Komponente.

background schließlich entspricht am ehesten dem ursprünglichen Anwendungsbereich von requestIdleCallback und ist für Aufgaben gedacht, die für den Nutzer keinerlei unmittelbare Relevanz haben, etwa Analytics oder Logging. Durch diese drei Stufen kann eine Anwendung deutlich differenzierter steuern, welche Hintergrundaufgabe im Zweifel zuerst an der Reihe ist, statt wie bei requestIdleCallback alle nicht-dringenden Aufgaben über einen Kamm zu scheren.

8. Praktisches Beispiel: Migration von rIC zu scheduler.postTask

Der Umstieg von requestIdleCallback auf scheduler.postTask() ist in den meisten Fällen unkompliziert, da sich das grundlegende Muster der unterbrechbaren Verarbeitung in kleinen Einheiten beibehalten lässt. Der wesentliche Unterschied besteht darin, dass anstelle von deadline.timeRemaining() nun die Prioritätsstufe explizit als Option übergeben wird und die Aufgabe als Promise behandelt werden kann, was sich besonders bei verketteten oder voneinander abhängigen Hintergrundaufgaben als deutlich lesbarer erweist.

In der Praxis empfiehlt sich eine Wrapper-Funktion, die bei vorhandenem scheduler-Objekt postTask() nutzt und andernfalls transparent auf requestIdleCallback zurückfällt, um Anwendungen schrittweise zu migrieren, ohne die Unterstützung für Browser ohne Scheduler-API zu verlieren.


async function scheduleBackgroundTask(task, priority = 'background') {
  if ('scheduler' in window && 'postTask' in scheduler) {
    return scheduler.postTask(task, { priority });
  }

  // Fallback für Browser ohne Scheduler API
  return new Promise((resolve) => {
    requestIdleCallback(() => resolve(task()), { timeout: 2000 });
  });
}

scheduleBackgroundTask(() => prefetchNextPageAssets(), 'background');
scheduleBackgroundTask(() => updateVisibleWidget(), 'user-visible');

9. Browser-Support und Strategie für den produktiven Einsatz

requestIdleCallback wird von allen gängigen Browsern außer Safari unterstützt, das bislang keine native Implementierung bietet, weshalb in produktiven Anwendungen häufig ein einfaches Polyfill auf Basis von setTimeout zum Einsatz kommt. Die Scheduler API ist neuer und aktuell primär in Chromium-basierten Browsern verfügbar, was eine sorgfältige Feature-Detection mit Fallback auf requestIdleCallback oder letztlich setTimeout erforderlich macht.

Für den produktiven Einsatz empfiehlt sich eine mehrstufige Fallback-Kette: Zuerst wird geprüft, ob die Scheduler API verfügbar ist, andernfalls wird auf requestIdleCallback zurückgegriffen, und als letzte Stufe dient ein einfacher setTimeout-Aufruf mit kurzer Verzögerung. So profitieren Nutzer moderner Browser von der präzisen Priorisierung, während die grundlegende Funktionalität in allen Umgebungen erhalten bleibt.

API Prioritätsstufen Abbruch möglich
requestIdleCallback Eine (undifferenziert) Nur über cancelIdleCallback
scheduler.postTask (user-blocking) Höchste Priorität Ja, über AbortSignal
scheduler.postTask (user-visible) Standardpriorität Ja, über AbortSignal
scheduler.postTask (background) Niedrigste Priorität Ja, über AbortSignal
setTimeout (Fallback) Keine echte Priorität Über clearTimeout

Mironsoft

Web Performance, Core Web Vitals und Ladezeit-Optimierung

Ladezeiten, die Nutzer nicht abspringen lassen, bevor die Seite überhaupt sichtbar ist?

Wir prüfen bestehende Webseiten auf langsame Core Web Vitals, aufgeblähte JavaScript-Bundles und ungenutzte Render-Blocker und bauen daraus eine Performance-Grundlage, die messbar bleibt statt nur einmalig gut auszusehen.

Performance-Audit

Core Web Vitals, Ladewasserfall und Render-Blocker systematisch messen und beheben.

Bundle-Optimierung

JavaScript- und CSS-Bundle-Größe sowie Code-Splitting gezielt reduzieren.

Monitoring-Aufbau

Kontinuierliches Performance-Monitoring statt einmaliger Momentaufnahme etablieren.

10. Zusammenfassung

requestIdleCallback

Ziel

Nicht-dringende Arbeit in Leerlaufzeit des Haupt-Threads verschieben

Klassisch

requestIdleCallback mit einer einzigen Prioritätsstufe

Modern

scheduler.postTask mit drei Prioritätsstufen und AbortSignal

Strategie

Feature-Detection mit Fallback-Kette bis setTimeout

11. FAQ: requestIdleCallback

1Wofür wird requestIdleCallback verwendet?
Um nicht-dringende Aufgaben wie Analytics, Prefetching oder unsichtbare UI-Updates gezielt in Phasen zu verschieben, in denen der Haupt-Thread ohnehin keine wichtigeren Aufgaben zu erledigen hat.
2Garantiert requestIdleCallback eine bestimmte Ausführungszeit?
Nein, es gibt keine feste Garantie. Ist der Browser durchgehend beschäftigt, kann sich die Ausführung verzögern, wobei ein optionaler timeout-Parameter eine maximale Wartezeit erzwingen kann.
3Was ist der Hauptunterschied zur Scheduler API?
Die Scheduler API bietet drei explizite Prioritätsstufen (user-blocking, user-visible, background) statt einer einzigen undifferenzierten Warteschlange und unterstützt zudem einen sauberen Abbruch über AbortSignal.
4Kann ich eine mit requestIdleCallback eingereihte Aufgabe abbrechen?
Ja, über cancelIdleCallback mit der von requestIdleCallback zurückgegebenen ID, allerdings ohne die Flexibilität, die ein AbortSignal in der Scheduler API bietet.
5Wird die Scheduler API von allen Browsern unterstützt?
Nein, sie ist derzeit primär in Chromium-basierten Browsern verfügbar. Für andere Browser empfiehlt sich ein Fallback auf requestIdleCallback oder letztlich setTimeout.
6Warum sollte ich die Arbeit innerhalb eines Callbacks in kleine Einheiten zerlegen?
Weil nur so deadline.timeRemaining() regelmäßig geprüft werden kann, um die Schleife rechtzeitig zu unterbrechen. Eine lang laufende, nicht unterbrechbare Operation blockiert den Haupt-Thread trotz requestIdleCallback.
7Ist scheduler.postTask ein direkter Ersatz für requestIdleCallback?
Weitgehend ja, mit dem Vorteil eines feineren Prioritätsmodells und Promise-basierter Nutzung, jedoch bei aktuell eingeschränkterem Browser-Support.
8Welche Prioritätsstufe eignet sich für Analytics-Events?
In der Regel background, da Analytics-Ereignisse keinerlei unmittelbare Relevanz für den Nutzer haben und problemlos verzögert werden können.
9Was passiert, wenn ich requestIdleCallback für eine zeitkritische Aufgabe verwende?
Das ist eine Fehlanwendung der API. Zeitkritische Aufgaben, insbesondere solche mit direkter Auswirkung auf Nutzerinteraktionen, sollten stets synchron oder mit der Prioritätsstufe user-blocking der Scheduler API behandelt werden.
10Brauche ich für requestIdleCallback ein Polyfill?
Für Safari ja, da dort bislang keine native Implementierung existiert. Ein einfaches setTimeout-basiertes Polyfill deckt die grundlegende Funktionalität für solche Fälle ab.