Hintergrundaufgaben im Browser mit der Scheduler API
Deine App fühlt sich schnell an – bis sie es plötzlich nicht mehr ist. Ein Nutzer klickt auf einen Button, und für 300 Millisekunden passiert nichts, weil der Main Thread mit etwas beschäftigt ist, das gerade gar nicht hätte passieren müssen. Genau dieses Kernproblem soll die Prioritized Task Scheduling API – allgemein bekannt als Scheduler API – lösen.
Dieser Artikel zeigt, wie scheduler.postTask() und scheduler.yield() dir praktische Kontrolle über das Scheduling auf dem Main Thread geben, wann du sie älteren Ansätzen vorziehen solltest und wie der aktuelle Stand der Browser-Unterstützung aussieht.
Die wichtigsten Erkenntnisse
- Die Scheduler API gibt dir feingranulare Kontrolle darüber, wann und wie Arbeit auf dem Main Thread ausgeführt wird, mit drei Prioritätsstufen:
user-blocking,user-visibleundbackground. scheduler.postTask()verschiebt Arbeit mit expliziter Priorität, währendscheduler.yield()lange Tasks aufteilt, damit der Browser zwischen den Schritten Benutzereingaben verarbeiten kann.- Keine der beiden Methoden verlagert Arbeit vom Main Thread weg. Für echte Parallelität sind Web Workers das Mittel der Wahl.
- Chromium-Browser und Firefox unterstützen die API; Safari hingegen nicht – plane daher immer einen
setTimeout-Fallback ein. - Intelligenteres Scheduling kann die Interaction to Next Paint (INP) und die allgemeine Reaktionsfähigkeit verbessern.
Warum Main-Thread-Scheduling wichtig ist
JavaScript läuft in einem einzigen Thread. Jede Skriptausführung, jedes DOM-Update und jeder Event-Handler konkurriert um dieselbe Ressource. Wenn ein langer Task diesen Thread blockiert, kann der Browser nicht auf Benutzereingaben reagieren – und das wirkt sich direkt negativ auf deinen Interaction to Next Paint (INP)-Wert aus.
Die traditionellen Workarounds haben jeweils ihre Grenzen:
setTimeout(fn, 0)verschiebt zwar Arbeit, gibt dir aber keinerlei Kontrolle über die Priorität. Es wird ausgeführt, egal wie ausgelastet der Thread ist.requestIdleCallback()wartet auf Leerlaufzeiten, was für niedrig priorisierte Arbeit nützlich ist – allerdings ohne Prioritätssystem, mit eingeschränkter Safari-Unterstützung und der Möglichkeit, unter Last beliebig lange verzögert zu werden.
Die Scheduler API adressiert beide Probleme.
Wie scheduler.postTask() funktioniert
scheduler.postTask() ist der Haupteinstiegspunkt der Prioritized Task Scheduling API. Es plant einen Callback so ein, dass er auf dem Main Thread mit einer angegebenen Prioritätsstufe ausgeführt wird.
scheduler.postTask(() => {
sendAnalyticsEvent('page_view');
}, { priority: 'background' });
Es gibt drei Prioritätsstufen:
user-blocking– höchste Priorität, für Arbeit, die eine Benutzerinteraktion direkt blockiertuser-visible– die Standardeinstellung, für Arbeit, die sich auf das auswirkt, was der Nutzer sieht, aber nicht blockierend istbackground– niedrigste Priorität, für Analytics, Prefetching oder Cleanup-Aufgaben
Das ist der entscheidende Unterschied zu älteren APIs: Du verschiebst Arbeit nicht nur, sondern teilst dem Browser mit, wie wichtig sie im Verhältnis zu allem anderen in der Queue ist.
scheduler.postTask() gibt ein Promise zurück, was die Verwendung mit async/await und eine saubere Fehlerbehandlung erleichtert:
try {
await scheduler.postTask(() => processLargeDataset(data), {
priority: 'background'
});
} catch (err) {
// Task was aborted or failed
console.warn('Task did not complete:', err);
}
Lange Tasks aufteilen mit scheduler.yield()
scheduler.yield() ist eine neuere Ergänzung, die ein anderes Problem löst: Was tust du, wenn du dich bereits inmitten eines langen Tasks befindest und dem Browser die Möglichkeit geben musst, Eingaben zu verarbeiten, bevor du fortfährst?
async function processItems(items) {
for (const item of items) {
processItem(item);
await scheduler.yield(); // yield back to the browser between items
}
}
Jedes await scheduler.yield() erzeugt einen Checkpoint, an dem der Browser ausstehende Benutzerinteraktionen verarbeiten kann, bevor deine Schleife fortgesetzt wird. Standardmäßig wird die Fortsetzung nach yield() mit user-visible-Priorität eingeplant, sie kann jedoch die Priorität eines umgebenden postTask()-Aufrufs erben. Dies ist eines der praktischsten Werkzeuge, um lange Tasks zu reduzieren, ohne deine gesamte Codebasis umstrukturieren zu müssen.
Discover how at OpenReplay.com.
Eine wichtige Klarstellung
Weder scheduler.postTask() noch scheduler.yield() verlagern Arbeit vom Main Thread weg. Auf diese Weise eingeplante Tasks laufen weiterhin auf dem Main Thread – sie werden lediglich intelligenter eingereiht und priorisiert. Wenn du echte parallele Ausführung benötigst, sind Web Workers das richtige Werkzeug.
Browser-Unterstützung
Die Unterstützung für die Scheduler API ist in Chromium-basierten Browsern (Chrome, Edge, Opera) solide. Firefox-Unterstützung kam deutlich später als die in Chromium, und Safari unterstützt die API derzeit weiterhin nicht. Du kannst die aktuelle Support-Matrix auf Can I Use überprüfen.
Ein minimaler Feature-Check vor der Verwendung:
if ('scheduler' in window && 'postTask' in scheduler) {
scheduler.postTask(myTask, { priority: 'background' });
} else {
// Fallback for Safari and older browsers
setTimeout(myTask, 0);
}
Speziell für scheduler.yield() solltest du den Support separat prüfen, bevor du dich im Produktivbetrieb darauf verlässt, da es später als postTask() ausgeliefert wurde und eine geringere Verbreitung hat:
async function safeYield() {
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
Wann du zur Scheduler API greifen solltest
Verwende scheduler.postTask(), wenn du nicht-kritische Arbeit – Analytics, Prefetching oder Verarbeitung nach dem Rendering – mit expliziter Prioritätskontrolle verschieben möchtest. Verwende scheduler.yield(), wenn du eine Schleife oder einen mehrstufigen Prozess hast, der die Verarbeitung von Eingaben blockieren könnte.
Wenn deine Nutzer hauptsächlich Chromium oder Firefox verwenden, ist die Scheduler API heute praxistauglich – sofern du Feature Detection und Fallbacks einsetzt. Für eine breitere Abdeckung solltest du den setTimeout-Fallback beibehalten, bis Safari nachzieht.
Fazit
Die Scheduler API schließt eine seit Langem bestehende Lücke darin, wie die Web-Plattform Arbeit auf dem Main Thread verwaltet. Statt sich auf grobe Werkzeuge wie setTimeout(fn, 0) oder das inkonsistent unterstützte requestIdleCallback() zu verlassen, kannst du nun deine Absicht ausdrücken: Dieser Task ist kritisch, jener kann warten, und diese Schleife sollte für Benutzereingaben pausieren. Das Ergebnis können flüssigere Interaktionen und bessere INP-Werte sein – mit vergleichsweise wenigen Code-Änderungen. Kombiniere die API mit einem Feature-Check und einem sinnvollen Fallback, und du kannst sie heute schon sicher einsetzen.
FAQs
Nein. Alle mit scheduler.postTask() eingeplanten Tasks werden weiterhin auf dem Main Thread ausgeführt. Die API ändert lediglich, wann und in welcher Reihenfolge Tasks laufen, indem sie ihnen Prioritäten zuweist. Wenn du echte Parallelität für CPU-intensive Arbeit benötigst, verwende stattdessen Web Workers, die Code in einem separaten Thread ausführen und über Nachrichten mit dem Main Thread kommunizieren.
Verwende scheduler.yield() innerhalb einer bestehenden Funktion oder Schleife, wenn du kurz pausieren möchtest, damit der Browser Benutzereingaben verarbeiten kann, und anschließend fortfahren willst. Verwende scheduler.postTask(), wenn du eine eigenständige Arbeitseinheit einplanen möchtest, die später mit einer bestimmten Priorität ausgeführt werden soll. Sie lösen verwandte, aber unterschiedliche Probleme: das Pausieren innerhalb eines Tasks gegenüber dem Einreihen neuer Tasks.
INP misst, wie schnell deine Seite auf Benutzerinteraktionen reagiert. Lange Tasks auf dem Main Thread sind die häufigste Ursache für schlechte INP-Werte. Indem die Scheduler API zwischen Schritten pausiert und nicht-kritischer Arbeit eine niedrigere Priorität zuweist, hilft sie, den Main Thread verfügbar zu halten, um Klicks, Tipps und Tastendrücke zeitnah zu verarbeiten, was die Interaktionslatenz reduzieren kann.
Ja, solange du einen Feature-Check und einen Fallback einbaust. Ein einfaches Muster ist, auf scheduler.postTask zu prüfen und auf setTimeout zurückzugreifen, wenn es nicht verfügbar ist. So funktioniert dein Code in Safari und älteren Browsern weiter, während Chromium- und Firefox-Nutzer vom priorisierten Scheduling profitieren. Polyfills existieren, sind aber für die grundlegende Nutzung in der Regel nicht erforderlich.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.