Ha valaha feldarabolt egy hosszú, main threadet blokkoló ciklust setTimeout(fn, 0)-val, hogy „engedjen levegőhöz” a böngészőt, akkor tudod azt az érzést, amikor működik ugyan, de fogalmad sincs, miért pont annyira gyorsult, amennyire. React is pontosan ezt csinálja évek óta a saját Scheduler csomagjában, csak MessageChannel-lel és a navigator.scheduling.isInputPending() API-val kombinálva, mert a natív böngésző soha nem adott ehhez rendes eszközt. Most már ad – csak épp majdnem senki nem tudja, hogy a trükk, amit eddig használtál, egy konkrét, mérhető hátránnyal jár, amit a natív megoldás pont emiatt épített be alapból.

A probléma, amit mindenki ismer
Egy 50ms-nél hosszabb JavaScript task blokkolja a main threadet: nem futhat le közben input-kezelés, nem frissülhet a UI, a Interaction to Next Paint romlik. A megoldás mindig ugyanaz volt: darabold fel a munkát, és add vissza időnként az irányítást a böngészőnek. Csak épp a rendelkezésre álló eszközök – setTimeout(0), requestAnimationFrame, requestIdleCallback – mind ötletes újrahasznosítások, nem kifejezetten erre tervezve.
scheduler.postTask: prioritás, nem csak halasztás
A Prioritized Task Scheduling API (Chrome 94, 2021 óta) ad egy globális scheduler objektumot három prioritási szinttel: user-blocking, user-visible (alapértelmezett) és background.
if ("scheduler" in globalThis) {
scheduler.postTask(() => renderVisibleList(), { priority: "user-visible" });
scheduler.postTask(() => sendAnalyticsBatch(), { priority: "background" });
}
A TaskController ráadásul futás közben is átprioritizálhatja a még el nem indult taskot – ezt eredetileg olyan use case-ekre szánták, mint amikor egy widget scrollozás közben látótérbe kerül, és onnantól user-blocking-ra kellene váltani a renderelését:
const controller = new TaskController({ priority: "user-visible" });
scheduler.postTask(() => renderWidget(), { signal: controller.signal });
viewportObserver.addEventListener("intersect", () => {
controller.setPriority("user-blocking");
});
Érdekes adalék: a Chrome saját telemetriája szerint a TaskController.setPriority() használata töredéke a sima postTask()-énak – ez a WebKit-es standards-positions vitában is felmerült konkrét számként. A legtöbb fejlesztő tehát nem bajlódik a dinamikus átprioritizálással, csak a statikus „ez most fontos, ez ráér” döntéssel – ami valljuk be, a use case-ek 90%-ában elég is.
A meglepő rész: scheduler.yield()
Ez a rész az, amitől érdemes végigolvasni a cikket. scheduler.yield() (Chrome 129, 2024 szeptember) első ránézésre csak egy Promise-visszaadó await-elhető pihenő pont egy hosszú ciklusban:
async function processRows(rows) {
for (const row of rows) {
renderRow(row);
if ("scheduler" in globalThis && "yield" in scheduler) {
await scheduler.yield();
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
}
Na ezt nem tudtam: a setTimeout(0) és a scheduler.yield() folytatása nem ugyanoda kerül a sorban. A Chrome fejlesztői blog cikke és a WICG explainer szerint setTimeout(0) után a folytatásod a sor végére kerül – minden már beütemezett task lefut előtte, akár egy harmadik féltől jövő script is. A scheduler.yield() folytatása viszont a saját task-od prioritási szintjén, az azonos vagy alacsonyabb prioritású új taskok elé kerül – vagyis a ciklusod folytatása nem versenyez hátrányból mindennel, ami közben becsatlakozott a sorba. Ez pont az a diagram, amit a cikk elején látsz: a felső sávban a setTimeout(0)-os folytatás a két közbeékelődött task mögé csúszik, az alsóban a yield()-es folytatás rögtön utánuk – tehát előttük – fut.
Ez azt jelenti, hogy egy hosszú listát renderelő ciklus scheduler.yield()-del úgy adja vissza a vezérlést a böngészőnek (hogy futhasson egy input esemény vagy egy repaint), hogy közben nem esik hátrébb a saját munkájával egy lassabb harmadik féltől jövő scripthez képest. Ezt egyébként Airbnb is publikálta saját mérésként még a postTask origin trial idején: a Total Blocking Time számottevő csökkenését mérték egy tipikus mobilos munkameneten, miután kép-karusszeleket, térképet és analitikai scripteket priorizáltak vele – ez az ő saját, kontrollálatlan méréseik, nem egy laborérték, úgyhogy inkább irányadó, mint bizonyíték.
Amiért még nem dobnám ki a régi trükköt
Ez most szép és okos API, de van pár dolog, ami miatt nem raknám be gondolkodás nélkül minden projektbe.
A Safari egyáltalán nem támogatja, sem a postTask-ot, sem a yield()-et. A WebKit standards-positions #361 ticket 2024 júniusa óta nyitva áll, formális pozíció nélkül – és a beszélgetésből kiderül, hogy még a metódusnevek konzisztenciáján is vitáznak (yield() vs. egy tervezett render()/wait() pár), tehát a specifikáció maga sincs lezárva. A Firefox is csak 2025 augusztusában, a 142-es verzióban kapta meg mindkettőt – három évvel a Chrome után. A webstatus.dev adatai szerint a Baseline-státusz jelenleg is „limited”, vagyis hivatalosan sem számít még széles körben elérhetőnek.
Van hivatalos polifill is, de a saját README-je vallja be, hogy setTimeout/MessageChannel/requestIdleCallback kombinációval nem tudja visszaadni a natív user-blocking prioritás valódi event loop-beli elsőbbségét – ott kötsz ki, ahol egyébként is voltál a setTimeout(0)-trükkel.
Amit ténylegesen csinálnék: feature-detection mögé rakva, progresszív enhancement-ként bevezetném ott, ahol tényleg van hosszú, feldarabolható munka – nagy táblázat vagy canvas renderelés, tömeges DOM-frissítés, kliens oldali keresés/szűrés nagy adathalmazon. A statikus három prioritási szint (postTask) simán megéri, a dinamikus TaskController.setPriority() átprioritizálást viszont csak akkor vezetném be, ha van rá konkrét, mért indok – a fent említett usage-adatok szerint a legtöbb helyen ez felesleges komplexitás. Sima CRUD-alkalmazásnál, ahol a leghosszabb task is 10ms alatt van, ez most még megoldás egy problémára, ami nálad nincs is.
Források
- MDN – Prioritized Task Scheduling API
- MDN – Scheduler.postTask()
- MDN – Scheduler.yield()
- Chrome for Developers – Use scheduler.yield() to break up long tasks
- WICG scheduling-apis – yield-and-continuation explainer
- WebKit standards-positions #361 – Prioritized Task Scheduling
- Mozilla standards-positions #546 – scheduler.postTask
- webstatus.dev – Scheduler API Baseline-státusz
- GoogleChromeLabs/scheduler-polyfill
- Airbnb Engineering – Building a Faster Web Experience with the postTask Scheduler (archivált)