[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities":1358},{"id":4,"title":5,"body":6,"description":1337,"extension":1338,"meta":1339,"navigation":1351,"path":1352,"seo":1353,"stem":1356,"__hash__":1357},"content\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002Findex.md","Using scheduler.postTask() Priorities",{"type":7,"value":8,"toc":1322},"minimark",[9,14,47,73,154,159,205,209,221,231,243,249,374,378,383,655,658,662,665,821,824,828,923,926,930,939,1023,1026,1147,1151,1158,1162,1205,1209,1229,1233,1252,1268,1280,1284,1307,1312,1315,1318],[10,11,13],"h1",{"id":12},"how-to-use-schedulerposttask-priorities-to-keep-interactions-fast","How to Use scheduler.postTask() Priorities to Keep Interactions Fast",[15,16,17,18,23,24,28,29,33,34,37,38,42,43,46],"p",{},"This guide extends ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F","Optimizing INP with scheduler.yield()"," in ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement"," from ",[30,31,32],"em",{},"splitting"," work to ",[30,35,36],{},"ordering"," it. ",[39,40,41],"code",{},"scheduler.yield()"," answers \"how do I let input through in the middle of my task?\". ",[39,44,45],{},"scheduler.postTask()"," answers the prior question: \"which of my tasks should run first, and which can wait?\"",[15,48,49,50,53,54,57,58,61,62,53,65,68,69,72],{},"Most pages queue work with ",[39,51,52],{},"setTimeout",", ",[39,55,56],{},"requestAnimationFrame",", promise microtasks and ",[39,59,60],{},"requestIdleCallback",". None of these lets you tell the browser that rendering a filter result matters more than serialising analytics, or that pre-computing recommendations can wait until everything else is done. The Prioritized Task Scheduling API does: every task is posted with one of three priorities — ",[39,63,64],{},"user-blocking",[39,66,67],{},"user-visible"," (the default), or ",[39,70,71],{},"background"," — and the browser's scheduler orders them accordingly, alongside its own input handling and rendering.",[15,74,75],{},[76,77,83,84,83,91,83,95,83,98,83,107,83,113,83,122,83,127,83,133,83,138,83,141,83,144,83,148,83,151,83],"svg",{"viewBox":78,"width":79,"role":80,"ariaLabel":81,"style":82},"0 0 760 218","100%","img","The three priority levels of scheduler.postTask with what kind of work belongs in each and how the browser treats them.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[85,86],"rect",{"className":87,"x":89,"y":89,"width":79,"height":79,"fill":90},[88],"svg-canvas","0","#ffffff",[92,93,94],"title",{},"The three postTask priorities",[96,97,81],"desc",{},[85,99],{"x":100,"y":100,"width":101,"height":102,"rx":103,"fill":104,"stroke":105,"style":106},"1","758","216","10","none","currentColor","stroke-opacity:0.18",[108,109,94],"text",{"x":110,"y":111,"fill":105,"style":112},"28.0","34.0","font-size:16px;font-weight:700",[85,114],{"x":110,"y":115,"width":116,"height":117,"rx":118,"fill":119,"stroke":120,"style":121},"56.0","704.0","40.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[108,123,64],{"x":124,"y":125,"fill":105,"style":126},"42.0","80.5","font-size:13px;font-weight:700",[108,128,132],{"x":129,"y":125,"fill":105,"style":130,"textAnchor":131},"143.7","font-size:12px","start","work the user is waiting on right now — the visible response to an interaction",[85,134],{"x":110,"y":135,"width":116,"height":117,"rx":118,"fill":136,"stroke":136,"style":137},"106.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[108,139,67],{"x":124,"y":140,"fill":105,"style":126},"130.5",[108,142,143],{"x":129,"y":140,"fill":105,"style":130,"textAnchor":131},"default: work with visible results that is not blocking an interaction",[85,145],{"x":110,"y":146,"width":116,"height":117,"rx":118,"fill":105,"stroke":105,"style":147},"156.0","fill-opacity:0.06;stroke-opacity:0.4",[108,149,71],{"x":124,"y":150,"fill":105,"style":126},"180.5",[108,152,153],{"x":129,"y":150,"fill":105,"style":130,"textAnchor":131},"analytics, prefetching, cache warming, logging — runs when nothing else is waiting",[155,156,158],"h2",{"id":157},"rapid-diagnosis","Rapid Diagnosis",[160,161,162,181,187,196],"ul",{},[163,164,165,169,170,53,173,176,177,180],"li",{},[166,167,168],"strong",{},"Inventory deferred work."," Search the codebase for ",[39,171,172],{},"setTimeout(",[39,174,175],{},"requestIdleCallback("," and ",[39,178,179],{},"queueMicrotask(",". Each is an implicit priority decision; most are made by accident.",[163,182,183,186],{},[166,184,185],{},"Look at what runs after interactions."," In a trace, after the interaction's paint, see which tasks run before the next interaction can be handled. Analytics and logging tasks queued at the same priority as UI work compete with it.",[163,188,189,192,193,195],{},[166,190,191],{},"Check for idle callbacks that never run."," On busy pages ",[39,194,60],{}," can be postponed for seconds; work that has a deadline but is queued as idle quietly misses it.",[163,197,198,83,201,204],{},[166,199,200],{},"Check feature support.",[39,202,203],{},"'scheduler' in globalThis && 'postTask' in scheduler"," — Chromium since 94 and Firefox 142 support it; Safari does not at the time of writing.",[155,206,208],{"id":207},"root-cause-analysis-why-unprioritised-work-hurts-inp","Root Cause Analysis: Why Unprioritised Work Hurts INP",[15,210,211,214,215,217,218,220],{},[166,212,213],{},"1. Everything is \"normal\" priority."," A ",[39,216,52],{}," for analytics and a ",[39,219,52],{}," for rendering a result list have the same priority. Whichever was queued first runs first.",[15,222,223,226,227,230],{},[166,224,225],{},"2. Microtasks cannot be interrupted."," Promise continuations run to completion before the browser can handle input or render. Long chains of ",[39,228,229],{},"then"," callbacks behave like one long task.",[15,232,233,83,236,238,239,242],{},[166,234,235],{},"3. Idle callbacks are too lazy for real work.",[39,237,60],{}," is perfect for genuinely optional work but is commonly misused for work that must happen soon, then hacked with ",[39,240,241],{},"timeout"," options that turn it back into a normal task.",[15,244,245,248],{},[166,246,247],{},"4. No cancellation."," Work queued for a view the user has already left still runs, consuming the thread during the next view's interactions.",[15,250,251],{},[76,252,83,255,83,258,83,261,83,263,83,266,83,268,83,273,83,279,83,286,83,290,83,294,83,297,83,301,83,305,83,309,83,313,83,316,83,319,83,321,83,323,83,325,83,328,83,331,83,334,83,341,83,347,83,351,83,355,83,358,83,361,83,365,83,369,83],{"viewBox":253,"width":79,"role":80,"ariaLabel":254,"style":82},"0 0 760 204","Two timelines of work after a click; without priorities analytics runs before the result render, with priorities the render runs first and analytics last.",[85,256],{"className":257,"x":89,"y":89,"width":79,"height":79,"fill":90},[88],[92,259,260],{},"Post-click work with and without priorities",[96,262,254],{},[85,264],{"x":100,"y":100,"width":101,"height":265,"rx":103,"fill":104,"stroke":105,"style":106},"202",[108,267,260],{"x":110,"y":111,"fill":105,"style":112},[108,269,272],{"x":110,"y":270,"fill":105,"style":271},"74.0","font-size:12.5px;font-weight:700","Unordered",[85,274],{"x":275,"y":115,"width":276,"height":277,"rx":278,"fill":136,"stroke":136,"style":137},"103.7","60.8","26.0","3",[108,280,285],{"x":281,"y":282,"fill":105,"style":283,"textAnchor":284},"134.1","73.5","font-size:11.5px","middle","handler",[85,287],{"x":288,"y":115,"width":289,"height":277,"rx":278,"fill":105,"stroke":105,"style":147},"166.0","164.7",[108,291,293],{"x":292,"y":282,"fill":105,"style":283,"textAnchor":284},"248.3","analytics",[85,295],{"x":296,"y":115,"width":289,"height":277,"rx":278,"fill":119,"stroke":120,"style":121},"332.1",[108,298,300],{"x":299,"y":282,"fill":105,"style":283,"textAnchor":284},"414.5","render results",[85,302],{"x":303,"y":115,"width":304,"height":277,"rx":278,"fill":105,"stroke":105,"style":147},"498.3","102.3",[108,306,308],{"x":307,"y":282,"fill":105,"style":283,"textAnchor":284},"549.5","prefetch",[108,310,312],{"x":110,"y":311,"fill":105,"style":271},"112.0","Prioritised",[85,314],{"x":275,"y":315,"width":276,"height":277,"rx":278,"fill":136,"stroke":136,"style":137},"94.0",[108,317,285],{"x":281,"y":318,"fill":105,"style":283,"textAnchor":284},"111.5",[85,320],{"x":288,"y":315,"width":289,"height":277,"rx":278,"fill":119,"stroke":120,"style":121},[108,322,300],{"x":292,"y":318,"fill":105,"style":283,"textAnchor":284},[85,324],{"x":296,"y":315,"width":304,"height":277,"rx":278,"fill":105,"stroke":105,"style":147},[108,326,308],{"x":327,"y":318,"fill":105,"style":283,"textAnchor":284},"383.3",[85,329],{"x":330,"y":315,"width":289,"height":277,"rx":278,"fill":105,"stroke":105,"style":147},"436.0",[108,332,293],{"x":333,"y":318,"fill":105,"style":283,"textAnchor":284},"518.3",[335,336],"line",{"x1":337,"y1":338,"x2":339,"y2":338,"stroke":105,"style":340},"102.9","134.0","726.0","stroke-opacity:0.4",[108,342,346],{"x":343,"y":344,"fill":345,"style":283,"textAnchor":284},"113.6","151.0","#51617a","0ms",[108,348,350],{"x":349,"y":344,"fill":345,"style":283,"textAnchor":284},"206.8","50ms",[108,352,354],{"x":353,"y":344,"fill":345,"style":283,"textAnchor":284},"310.6","100ms",[108,356,357],{"x":299,"y":344,"fill":345,"style":283,"textAnchor":284},"150ms",[108,359,360],{"x":333,"y":344,"fill":345,"style":283,"textAnchor":284},"200ms",[108,362,364],{"x":363,"y":344,"fill":345,"style":283,"textAnchor":284},"622.2","250ms",[108,366,368],{"x":367,"y":344,"fill":345,"style":283,"textAnchor":284},"715.3","300ms",[108,370,373],{"x":110,"y":371,"fill":345,"style":372},"182.0","font-size:12.5px","Same total work. With priorities, the visible result is ready 80ms sooner and background work no longer delays it.",[155,375,377],{"id":376},"step-by-step-resolution","Step-by-Step Resolution",[379,380,382],"h3",{"id":381},"_1-wrap-posttask-with-a-safe-fallback","1. Wrap postTask with a safe fallback",[384,385,390],"pre",{"className":386,"code":387,"language":388,"meta":389,"style":389},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F schedule.js\nexport function postTask(fn, { priority = 'user-visible', signal, delay = 0 } = {}) {\n  if (globalThis.scheduler?.postTask) return scheduler.postTask(fn, { priority, signal, delay });\n  return new Promise((resolve, reject) => {\n    const run = () => (signal?.aborted ? reject(signal.reason) : resolve(fn()));\n    if (priority === 'background' && 'requestIdleCallback' in globalThis) {\n      requestIdleCallback(run, { timeout: 2000 + delay });\n    } else {\n      setTimeout(run, delay);\n    }\n  });\n}\n\u002F\u002F trade-off: the fallback approximates priorities but cannot reorder already-\n\u002F\u002F queued tasks the way the native scheduler does. On Safari, 'user-blocking'\n\u002F\u002F and 'user-visible' behave identically.\n","javascript","",[39,391,392,400,460,481,513,554,581,599,610,619,625,631,637,643,649],{"__ignoreMap":389},[393,394,396],"span",{"class":335,"line":395},1,[393,397,399],{"class":398},"sjfSM","\u002F\u002F schedule.js\n",[393,401,403,407,410,414,418,422,425,428,431,435,437,440,442,445,447,451,454,457],{"class":335,"line":402},2,[393,404,406],{"class":405},"sPARh","export",[393,408,409],{"class":405}," function",[393,411,413],{"class":412},"smZ65"," postTask",[393,415,417],{"class":416},"saISM","(",[393,419,421],{"class":420},"sQw3B","fn",[393,423,424],{"class":416},", { ",[393,426,427],{"class":420},"priority",[393,429,430],{"class":405}," =",[393,432,434],{"class":433},"sZ8jY"," 'user-visible'",[393,436,53],{"class":416},[393,438,439],{"class":420},"signal",[393,441,53],{"class":416},[393,443,444],{"class":420},"delay",[393,446,430],{"class":405},[393,448,450],{"class":449},"sPXB4"," 0",[393,452,453],{"class":416}," } ",[393,455,456],{"class":405},"=",[393,458,459],{"class":416}," {}) {\n",[393,461,463,466,469,472,475,478],{"class":335,"line":462},3,[393,464,465],{"class":405},"  if",[393,467,468],{"class":416}," (globalThis.scheduler?.postTask) ",[393,470,471],{"class":405},"return",[393,473,474],{"class":416}," scheduler.",[393,476,477],{"class":412},"postTask",[393,479,480],{"class":416},"(fn, { priority, signal, delay });\n",[393,482,484,487,490,493,496,499,501,504,507,510],{"class":335,"line":483},4,[393,485,486],{"class":405},"  return",[393,488,489],{"class":405}," new",[393,491,492],{"class":449}," Promise",[393,494,495],{"class":416},"((",[393,497,498],{"class":420},"resolve",[393,500,53],{"class":416},[393,502,503],{"class":420},"reject",[393,505,506],{"class":416},") ",[393,508,509],{"class":405},"=>",[393,511,512],{"class":416}," {\n",[393,514,516,519,522,524,527,529,532,535,538,541,544,547,549,551],{"class":335,"line":515},5,[393,517,518],{"class":405},"    const",[393,520,521],{"class":412}," run",[393,523,430],{"class":405},[393,525,526],{"class":416}," () ",[393,528,509],{"class":405},[393,530,531],{"class":416}," (signal?.aborted ",[393,533,534],{"class":405},"?",[393,536,537],{"class":412}," reject",[393,539,540],{"class":416},"(signal.reason) ",[393,542,543],{"class":405},":",[393,545,546],{"class":412}," resolve",[393,548,417],{"class":416},[393,550,421],{"class":412},[393,552,553],{"class":416},"()));\n",[393,555,557,560,563,566,569,572,575,578],{"class":335,"line":556},6,[393,558,559],{"class":405},"    if",[393,561,562],{"class":416}," (priority ",[393,564,565],{"class":405},"===",[393,567,568],{"class":433}," 'background'",[393,570,571],{"class":405}," &&",[393,573,574],{"class":433}," 'requestIdleCallback'",[393,576,577],{"class":405}," in",[393,579,580],{"class":416}," globalThis) {\n",[393,582,584,587,590,593,596],{"class":335,"line":583},7,[393,585,586],{"class":412},"      requestIdleCallback",[393,588,589],{"class":416},"(run, { timeout: ",[393,591,592],{"class":449},"2000",[393,594,595],{"class":405}," +",[393,597,598],{"class":416}," delay });\n",[393,600,602,605,608],{"class":335,"line":601},8,[393,603,604],{"class":416},"    } ",[393,606,607],{"class":405},"else",[393,609,512],{"class":416},[393,611,613,616],{"class":335,"line":612},9,[393,614,615],{"class":412},"      setTimeout",[393,617,618],{"class":416},"(run, delay);\n",[393,620,622],{"class":335,"line":621},10,[393,623,624],{"class":416},"    }\n",[393,626,628],{"class":335,"line":627},11,[393,629,630],{"class":416},"  });\n",[393,632,634],{"class":335,"line":633},12,[393,635,636],{"class":416},"}\n",[393,638,640],{"class":335,"line":639},13,[393,641,642],{"class":398},"\u002F\u002F trade-off: the fallback approximates priorities but cannot reorder already-\n",[393,644,646],{"class":335,"line":645},14,[393,647,648],{"class":398},"\u002F\u002F queued tasks the way the native scheduler does. On Safari, 'user-blocking'\n",[393,650,652],{"class":335,"line":651},15,[393,653,654],{"class":398},"\u002F\u002F and 'user-visible' behave identically.\n",[15,656,657],{},"Expected outcome: one call site for all deferred work, with intent encoded in every call.",[379,659,661],{"id":660},"_2-classify-the-work-around-each-interaction","2. Classify the work around each interaction",[15,663,664],{},"After a click, typically three kinds of work follow: the visible response, follow-up UI (secondary panels, counters), and invisible bookkeeping. Post each at its priority.",[384,666,668],{"className":386,"code":667,"language":388,"meta":389,"style":389},"filterButton.addEventListener('click', () => {\n  const query = readFilters();                                       \u002F\u002F cheap, synchronous\n  postTask(() => renderResults(query), { priority: 'user-blocking' });\n  postTask(() => updateFacetCounts(query), { priority: 'user-visible' });\n  postTask(() => track('filter_applied', query), { priority: 'background' });\n  postTask(() => prefetchNextPage(query), { priority: 'background', delay: 500 });\n});\n\u002F\u002F trade-off: posting the render as a separate task means the handler returns\n\u002F\u002F before results appear. The interaction's paint shows the pressed state, not\n\u002F\u002F the results — fine for INP, but show a pending indicator so it is clear\n\u002F\u002F something is happening.\n",[39,669,670,690,709,731,749,773,796,801,806,811,816],{"__ignoreMap":389},[393,671,672,675,678,680,683,686,688],{"class":335,"line":395},[393,673,674],{"class":416},"filterButton.",[393,676,677],{"class":412},"addEventListener",[393,679,417],{"class":416},[393,681,682],{"class":433},"'click'",[393,684,685],{"class":416},", () ",[393,687,509],{"class":405},[393,689,512],{"class":416},[393,691,692,695,698,700,703,706],{"class":335,"line":402},[393,693,694],{"class":405},"  const",[393,696,697],{"class":449}," query",[393,699,430],{"class":405},[393,701,702],{"class":412}," readFilters",[393,704,705],{"class":416},"();                                       ",[393,707,708],{"class":398},"\u002F\u002F cheap, synchronous\n",[393,710,711,714,717,719,722,725,728],{"class":335,"line":462},[393,712,713],{"class":412},"  postTask",[393,715,716],{"class":416},"(() ",[393,718,509],{"class":405},[393,720,721],{"class":412}," renderResults",[393,723,724],{"class":416},"(query), { priority: ",[393,726,727],{"class":433},"'user-blocking'",[393,729,730],{"class":416}," });\n",[393,732,733,735,737,739,742,744,747],{"class":335,"line":483},[393,734,713],{"class":412},[393,736,716],{"class":416},[393,738,509],{"class":405},[393,740,741],{"class":412}," updateFacetCounts",[393,743,724],{"class":416},[393,745,746],{"class":433},"'user-visible'",[393,748,730],{"class":416},[393,750,751,753,755,757,760,762,765,768,771],{"class":335,"line":515},[393,752,713],{"class":412},[393,754,716],{"class":416},[393,756,509],{"class":405},[393,758,759],{"class":412}," track",[393,761,417],{"class":416},[393,763,764],{"class":433},"'filter_applied'",[393,766,767],{"class":416},", query), { priority: ",[393,769,770],{"class":433},"'background'",[393,772,730],{"class":416},[393,774,775,777,779,781,784,786,788,791,794],{"class":335,"line":556},[393,776,713],{"class":412},[393,778,716],{"class":416},[393,780,509],{"class":405},[393,782,783],{"class":412}," prefetchNextPage",[393,785,724],{"class":416},[393,787,770],{"class":433},[393,789,790],{"class":416},", delay: ",[393,792,793],{"class":449},"500",[393,795,730],{"class":416},[393,797,798],{"class":335,"line":583},[393,799,800],{"class":416},"});\n",[393,802,803],{"class":335,"line":601},[393,804,805],{"class":398},"\u002F\u002F trade-off: posting the render as a separate task means the handler returns\n",[393,807,808],{"class":335,"line":612},[393,809,810],{"class":398},"\u002F\u002F before results appear. The interaction's paint shows the pressed state, not\n",[393,812,813],{"class":335,"line":621},[393,814,815],{"class":398},"\u002F\u002F the results — fine for INP, but show a pending indicator so it is clear\n",[393,817,818],{"class":335,"line":627},[393,819,820],{"class":398},"\u002F\u002F something is happening.\n",[15,822,823],{},"Expected outcome: visible results render first; analytics and prefetching no longer compete with them or with the user's next interaction.",[379,825,827],{"id":826},"_3-cancel-work-that-is-no-longer-wanted","3. Cancel work that is no longer wanted",[384,829,831],{"className":386,"code":830,"language":388,"meta":389,"style":389},"let controller;\nfunction onQueryChange(q) {\n  controller?.abort();                                   \u002F\u002F drop queued work for the old query\n  controller = new TaskController({ priority: 'user-visible' });\n  postTask(() => computeSuggestions(q), { signal: controller.signal });\n}\n\u002F\u002F trade-off: TaskController is part of the same API and missing where postTask\n\u002F\u002F is missing; the wrapper above accepts a plain AbortSignal, which cancels but\n\u002F\u002F cannot re-prioritise. Feature-detect TaskController before using setPriority.\n",[39,832,833,841,857,871,890,904,908,913,918],{"__ignoreMap":389},[393,834,835,838],{"class":335,"line":395},[393,836,837],{"class":405},"let",[393,839,840],{"class":416}," controller;\n",[393,842,843,846,849,851,854],{"class":335,"line":402},[393,844,845],{"class":405},"function",[393,847,848],{"class":412}," onQueryChange",[393,850,417],{"class":416},[393,852,853],{"class":420},"q",[393,855,856],{"class":416},") {\n",[393,858,859,862,865,868],{"class":335,"line":462},[393,860,861],{"class":416},"  controller?.",[393,863,864],{"class":412},"abort",[393,866,867],{"class":416},"();                                   ",[393,869,870],{"class":398},"\u002F\u002F drop queued work for the old query\n",[393,872,873,876,878,880,883,886,888],{"class":335,"line":483},[393,874,875],{"class":416},"  controller ",[393,877,456],{"class":405},[393,879,489],{"class":405},[393,881,882],{"class":412}," TaskController",[393,884,885],{"class":416},"({ priority: ",[393,887,746],{"class":433},[393,889,730],{"class":416},[393,891,892,894,896,898,901],{"class":335,"line":515},[393,893,713],{"class":412},[393,895,716],{"class":416},[393,897,509],{"class":405},[393,899,900],{"class":412}," computeSuggestions",[393,902,903],{"class":416},"(q), { signal: controller.signal });\n",[393,905,906],{"class":335,"line":556},[393,907,636],{"class":416},[393,909,910],{"class":335,"line":583},[393,911,912],{"class":398},"\u002F\u002F trade-off: TaskController is part of the same API and missing where postTask\n",[393,914,915],{"class":335,"line":601},[393,916,917],{"class":398},"\u002F\u002F is missing; the wrapper above accepts a plain AbortSignal, which cancels but\n",[393,919,920],{"class":335,"line":612},[393,921,922],{"class":398},"\u002F\u002F cannot re-prioritise. Feature-detect TaskController before using setPriority.\n",[15,924,925],{},"Expected outcome: stale tasks never run, freeing the thread for current interactions.",[379,927,929],{"id":928},"_4-re-prioritise-when-the-users-focus-changes","4. Re-prioritise when the user's focus changes",[15,931,932,935,936,938],{},[39,933,934],{},"TaskController"," lets you raise or lower priority after posting — for instance, promoting a background prefetch of a panel's data to ",[39,937,64],{}," when the user opens that panel.",[384,940,942],{"className":386,"code":941,"language":388,"meta":389,"style":389},"const panelData = new TaskController({ priority: 'background' });\nconst pending = scheduler.postTask(loadPanelData, { signal: panelData.signal });\npanelToggle.addEventListener('click', () => panelData.setPriority('user-blocking'));\n\u002F\u002F trade-off: re-prioritising only affects tasks that have not started yet. If\n\u002F\u002F loadPanelData is itself long, split it with scheduler.yield() so the promoted\n\u002F\u002F priority applies to its remaining chunks.\n",[39,943,944,964,980,1008,1013,1018],{"__ignoreMap":389},[393,945,946,949,952,954,956,958,960,962],{"class":335,"line":395},[393,947,948],{"class":405},"const",[393,950,951],{"class":449}," panelData",[393,953,430],{"class":405},[393,955,489],{"class":405},[393,957,882],{"class":412},[393,959,885],{"class":416},[393,961,770],{"class":433},[393,963,730],{"class":416},[393,965,966,968,971,973,975,977],{"class":335,"line":402},[393,967,948],{"class":405},[393,969,970],{"class":449}," pending",[393,972,430],{"class":405},[393,974,474],{"class":416},[393,976,477],{"class":412},[393,978,979],{"class":416},"(loadPanelData, { signal: panelData.signal });\n",[393,981,982,985,987,989,991,993,995,998,1001,1003,1005],{"class":335,"line":462},[393,983,984],{"class":416},"panelToggle.",[393,986,677],{"class":412},[393,988,417],{"class":416},[393,990,682],{"class":433},[393,992,685],{"class":416},[393,994,509],{"class":405},[393,996,997],{"class":416}," panelData.",[393,999,1000],{"class":412},"setPriority",[393,1002,417],{"class":416},[393,1004,727],{"class":433},[393,1006,1007],{"class":416},"));\n",[393,1009,1010],{"class":335,"line":483},[393,1011,1012],{"class":398},"\u002F\u002F trade-off: re-prioritising only affects tasks that have not started yet. If\n",[393,1014,1015],{"class":335,"line":515},[393,1016,1017],{"class":398},"\u002F\u002F loadPanelData is itself long, split it with scheduler.yield() so the promoted\n",[393,1019,1020],{"class":335,"line":556},[393,1021,1022],{"class":398},"\u002F\u002F priority applies to its remaining chunks.\n",[15,1024,1025],{},"Expected outcome: speculative work becomes urgent exactly when it starts to matter.",[15,1027,1028],{},[76,1029,83,1032,83,1035,83,1038,83,1040,83,1043,83,1045,83,1049,83,1054,83,1058,83,1062,83,1065,83,1069,83,1073,83,1077,83,1079,83,1081,83,1083,83,1086,83,1089,83,1093,83,1095,83,1097,83,1099,83,1101,83,1104,83,1108,83,1110,83,1112,83,1114,83,1116,83,1119,83,1123,83,1125,83,1128,83,1130,83,1132,83,1135,83,1139,83,1141,83,1143,83,1145,83],{"viewBox":1030,"width":79,"role":80,"ariaLabel":1031,"style":82},"0 0 760 258","Recommended postTask priority for common categories of frontend work, with the fallback used when the API is missing.",[85,1033],{"className":1034,"x":89,"y":89,"width":79,"height":79,"fill":90},[88],[92,1036,1037],{},"Where common work belongs",[96,1039,1031],{},[85,1041],{"x":100,"y":100,"width":101,"height":1042,"rx":103,"fill":104,"stroke":105,"style":106},"256",[108,1044,1037],{"x":110,"y":111,"fill":105,"style":112},[85,1046],{"x":110,"y":115,"width":1047,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},"210.0","30.0",[108,1050,1053],{"x":1051,"y":1052,"fill":105,"style":271,"textAnchor":131},"38.0","75.5","Work",[85,1055],{"x":1056,"y":115,"width":1057,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},"238.0","247.0",[108,1059,1061],{"x":1060,"y":1052,"fill":105,"style":271,"textAnchor":284},"361.5","Priority",[85,1063],{"x":1064,"y":115,"width":1057,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},"485.0",[108,1066,1068],{"x":1067,"y":1052,"fill":105,"style":271,"textAnchor":284},"608.5","Fallback",[85,1070],{"x":110,"y":1071,"width":1047,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},"86.0","stroke-opacity:0.35",[108,1074,1076],{"x":1051,"y":1075,"fill":105,"style":271,"textAnchor":131},"105.5","Rendering the response to a click",[85,1078],{"x":1056,"y":1071,"width":1057,"height":1048,"rx":89,"fill":119,"stroke":120,"style":121},[108,1080,64],{"x":1060,"y":1075,"fill":105,"style":130,"textAnchor":284},[85,1082],{"x":1064,"y":1071,"width":1057,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},[108,1084,1085],{"x":1067,"y":1075,"fill":105,"style":130,"textAnchor":284},"setTimeout 0",[85,1087],{"x":110,"y":1088,"width":1047,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},"116.0",[108,1090,1092],{"x":1051,"y":1091,"fill":105,"style":271,"textAnchor":131},"135.5","Secondary UI updates",[85,1094],{"x":1056,"y":1088,"width":1057,"height":1048,"rx":89,"fill":136,"stroke":136,"style":137},[108,1096,67],{"x":1060,"y":1091,"fill":105,"style":130,"textAnchor":284},[85,1098],{"x":1064,"y":1088,"width":1057,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},[108,1100,1085],{"x":1067,"y":1091,"fill":105,"style":130,"textAnchor":284},[85,1102],{"x":110,"y":1103,"width":1047,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},"146.0",[108,1105,1107],{"x":1051,"y":1106,"fill":105,"style":271,"textAnchor":131},"165.5","Analytics and logging",[85,1109],{"x":1056,"y":1103,"width":1057,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},[108,1111,71],{"x":1060,"y":1106,"fill":105,"style":130,"textAnchor":284},[85,1113],{"x":1064,"y":1103,"width":1057,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},[108,1115,60],{"x":1067,"y":1106,"fill":105,"style":130,"textAnchor":284},[85,1117],{"x":110,"y":1118,"width":1047,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},"176.0",[108,1120,1122],{"x":1051,"y":1121,"fill":105,"style":271,"textAnchor":131},"195.5","Prefetching and cache warming",[85,1124],{"x":1056,"y":1118,"width":1057,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},[108,1126,1127],{"x":1060,"y":1121,"fill":105,"style":130,"textAnchor":284},"background + delay",[85,1129],{"x":1064,"y":1118,"width":1057,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},[108,1131,60],{"x":1067,"y":1121,"fill":105,"style":130,"textAnchor":284},[85,1133],{"x":110,"y":1134,"width":1047,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},"206.0",[108,1136,1138],{"x":1051,"y":1137,"fill":105,"style":271,"textAnchor":131},"225.5","Cleanup of a previous view",[85,1140],{"x":1056,"y":1134,"width":1057,"height":1048,"rx":89,"fill":105,"stroke":105,"style":147},[108,1142,71],{"x":1060,"y":1137,"fill":105,"style":130,"textAnchor":284},[85,1144],{"x":1064,"y":1134,"width":1057,"height":1048,"rx":89,"fill":104,"stroke":105,"style":1072},[108,1146,60],{"x":1067,"y":1137,"fill":105,"style":130,"textAnchor":284},[155,1148,1150],{"id":1149},"verification","Verification",[15,1152,1153,1154,1157],{},"Record the interaction and its aftermath in a throttled trace. Tasks should run in priority order after the interaction; background work should appear only after visible work completes. Measure the time from click to results painted with User Timing marks before and after: it should fall even though total work is unchanged. In the field, INP's processing duration rarely changes from prioritisation alone — the gain shows in ",[30,1155,1156],{},"subsequent"," interactions, whose input delay no longer includes background work, and in time-to-result metrics you define.",[155,1159,1161],{"id":1160},"how-posttask-interacts-with-scheduleryield","How postTask Interacts with scheduler.yield()",[15,1163,1164,1165,1167,1168,1170,1171,1173,1174,1176,1177,1179,1180,1183,1184,1186,1187,1189,1190,1193,1194,1196,1197,1199,1200,1204],{},"The two APIs compose. A task posted with ",[39,1166,477],{}," at a given priority can call ",[39,1169,41],{}," inside; the continuation inherits the task's priority, so a ",[39,1172,71],{}," job that yields resumes as background work and a ",[39,1175,64],{}," job resumes ahead of lower-priority tasks. That inheritance is the main reason to prefer ",[39,1178,477],{}," + ",[39,1181,1182],{},"yield"," over ",[39,1185,52],{}," chains: the priority survives across yield points. Use ",[39,1188,477],{}," to decide ",[30,1191,1192],{},"when"," a job starts and at what importance, and ",[39,1195,1182],{}," inside the job to keep each chunk under ",[39,1198,350],{},". ",[19,1201,1203],{"href":1202},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fscheduler-yield-vs-settimeout-0-for-yielding\u002F","scheduler.yield() vs setTimeout(0)"," covers the yield side in detail.",[155,1206,1208],{"id":1207},"migrating-an-existing-codebase","Migrating an Existing Codebase",[15,1210,1211,1212,1215,1216,1219,1220,1222,1223,1225,1226,1228],{},"Adopting priorities is easiest incrementally. Start by replacing every ",[39,1213,1214],{},"setTimeout(fn, 0)"," used for deferral with ",[39,1217,1218],{},"postTask(fn)"," at the default ",[39,1221,67],{}," priority — behaviour is essentially unchanged, but every deferral now goes through one function you can instrument. Next, find the analytics, logging and prefetch call sites and move them to ",[39,1224,71],{},"; this is where most of the benefit lies, because those calls are numerous and rarely urgent. Finally, look at the interactions your RUM flags as slowest and split their follow-up work into ",[39,1227,64],{}," for the visible response and lower priorities for everything else. Instrument the wrapper to count tasks by priority and duration in development, so you can see whether background work is actually staying in the background.",[155,1230,1232],{"id":1231},"faq","FAQ",[1234,1235,1238,1242],"details",{"className":1236},[1237],"faq-item",[1239,1240,1241],"summary",{},"Does user-blocking priority run before input handling?",[15,1243,1244,1245,1247,1248,1251],{},"No. Browser input handling and rendering remain the browser's own concerns; ",[39,1246,64],{}," tasks are prioritised among ",[30,1249,1250],{},"your"," tasks and roughly alongside rendering opportunities, but they do not preempt event dispatch. That is intentional: the API helps you order your work without letting a page starve the user's input.",[1234,1253,1255,1258],{"className":1254},[1237],[1239,1256,1257],{},"Can background tasks starve?",[15,1259,1260,1261,1263,1264,1267],{},"Yes, on a page that is constantly busy at higher priorities. The browser does not guarantee background tasks run within any time. For work with a deadline — sending analytics before the page unloads, for example — use ",[39,1262,67],{}," or send on ",[39,1265,1266],{},"visibilitychange"," rather than relying on a background task.",[1234,1269,1271,1274],{"className":1270},[1237],[1239,1272,1273],{},"Should frameworks' own scheduling be replaced with postTask?",[15,1275,1276,1277,1279],{},"No. React's scheduler and Vue's async update queue manage their own ordering; wrapping their internals is fragile. Use ",[39,1278,477],{}," for your application-level work around the framework — data processing, analytics, prefetching — and let the framework schedule rendering.",[155,1281,1283],{"id":1282},"related","Related",[160,1285,1286,1293,1300],{},[163,1287,1288,1292],{},[19,1289,1291],{"href":1290},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers\u002F","Polyfilling scheduler.yield() across browsers"," — keeping the API surface uniform.",[163,1294,1295,1299],{},[19,1296,1298],{"href":1297},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-vue-event-handlers\u002F","Breaking up long tasks in Vue event handlers"," — applying priorities in a Vue app.",[163,1301,1302,1306],{},[19,1303,1305],{"href":1304},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Fdebouncing-vs-yielding-for-input-events\u002F","Debouncing vs yielding for input events"," — reducing how often work is posted at all.",[1308,1309,1311],"script",{"type":1310},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Use scheduler.postTask() Priorities to Keep Interactions Fast\",\n  \"description\": \"How the three postTask priorities order your work against input and rendering, with patterns for deferring, cancelling and re-prioritising tasks.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Wrap postTask with a safe fallback\",\n      \"text\": \"Expected outcome: one call site for all deferred work, with intent encoded in every call.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Classify the work around each interaction\",\n      \"text\": \"After a click, typically three kinds of work follow: the visible response, follow-up UI (secondary panels, counters), and invisible bookkeeping.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Cancel work that is no longer wanted\",\n      \"text\": \"Expected outcome: stale tasks never run, freeing the thread for current interactions.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Re-prioritise when the user's focus changes\",\n      \"text\": \"TaskController lets you raise or lower priority after posting — for instance, promoting a background prefetch of a panel's data to user-blocking when the user opens that panel.\"\n    }\n  ]\n}\n",[1308,1313,1314],{"type":1310},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Use scheduler.postTask() Priorities to Keep Interactions Fast\",\n  \"description\": \"How the three postTask priorities order your work against input and rendering, with patterns for deferring, cancelling and re-prioritising tasks.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002F\"\n  }\n}\n",[1308,1316,1317],{"type":1310},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Optimizing INP with scheduler.yield()\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Using scheduler.postTask() Priorities\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002F\"\n    }\n  ]\n}\n",[1319,1320,1321],"style",{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":389,"searchDepth":402,"depth":402,"links":1323},[1324,1325,1326,1332,1333,1334,1335,1336],{"id":157,"depth":402,"text":158},{"id":207,"depth":402,"text":208},{"id":376,"depth":402,"text":377,"children":1327},[1328,1329,1330,1331],{"id":381,"depth":462,"text":382},{"id":660,"depth":462,"text":661},{"id":826,"depth":462,"text":827},{"id":928,"depth":462,"text":929},{"id":1149,"depth":402,"text":1150},{"id":1160,"depth":402,"text":1161},{"id":1207,"depth":402,"text":1208},{"id":1231,"depth":402,"text":1232},{"id":1282,"depth":402,"text":1283},"How the three postTask priorities order your work against input and rendering, with patterns for deferring, cancelling and re-prioritising tasks.","md",{"slug":1340,"type":1341,"breadcrumb":1342,"datePublished":1350,"dateModified":1350},"using-scheduler-posttask-priorities","article",[1343,1346,1347,1348],{"name":1344,"url":1345},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1349},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities",{"title":1354,"description":1355},"Using scheduler.postTask() Priorities to Protect INP","Label work as user-blocking, user-visible or background with scheduler.postTask so the browser runs interaction-critical work first and keeps INP under 200ms.","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002Findex","eTN6XrJ6QD9DS2j_aIWLj9_-J78zxjxw40sgiYsZzwY",[1359,1363],{"title":1360,"path":1361,"stem":1362,"children":-1},"scheduler.yield() vs setTimeout(0) for Yielding","\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fscheduler-yield-vs-settimeout-0-for-yielding","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fscheduler-yield-vs-settimeout-0-for-yielding\u002Findex",{"title":1364,"path":1365,"stem":1366,"children":-1},"Profiling Event Handlers for INP","\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Findex",1791308073760]