[{"data":1,"prerenderedAt":1342},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers":1333},{"id":4,"title":5,"body":6,"description":1312,"extension":1313,"meta":1314,"navigation":1326,"path":1327,"seo":1328,"stem":1331,"__hash__":1332},"content\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers\u002Findex.md","Polyfilling scheduler.yield() Across Browsers",{"type":7,"value":8,"toc":1297},"minimark",[9,14,38,62,232,237,281,285,294,304,313,319,448,452,457,654,660,664,815,818,822,835,875,878,882,885,1027,1030,1160,1164,1170,1174,1184,1188,1201,1205,1223,1235,1255,1259,1282,1287,1290,1293],[10,11,13],"h1",{"id":12},"how-to-polyfill-scheduleryield-across-browsers-without-losing-its-benefits","How to Polyfill scheduler.yield() Across Browsers Without Losing Its Benefits",[15,16,17,18,23,24,28,29,33,34,37],"p",{},"This guide covers the cross-browser side of ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002F","Optimizing INP with scheduler.yield()",", part of ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". ",[30,31,32],"code",{},"scheduler.yield()"," ships in Chromium 129+ and Firefox 142+. Safari does not support it at the time of writing, and older Chromium builds still appear in field data. Code that calls it unguarded throws a ",[30,35,36],{},"TypeError"," and breaks the interaction entirely — the worst possible INP outcome.",[15,39,40,41,44,45,48,49,52,53,56,57,61],{},"There are two sound approaches: a tiny inline fallback that yields via a macrotask, or a full polyfill such as ",[30,42,43],{},"scheduler-polyfill"," that provides ",[30,46,47],{},"postTask",", ",[30,50,51],{},"TaskController"," and ",[30,54,55],{},"yield"," with emulated priorities. The right choice depends on whether you use priorities and how much you care about bytes on the critical path. Either way, it helps to understand what a polyfill ",[58,59,60],"em",{},"cannot"," do: it cannot give your continuation priority over other queued tasks, because that requires the browser's scheduler.",[15,63,64],{},[65,66,72,73,72,80,72,84,72,87,72,96,72,102,72,108,72,115,72,119,72,124,72,127,72,131,72,134,72,137,72,141,72,145,72,149,72,153,72,155,72,157,72,159,72,161,72,165,72,169,72,173,72,175,72,178,72,183,72,186,72,188,72,191,72,194,72,198,72,200,72,202,72,204,72,206,72,208,72,211,72,214,72,218,72,220,72,222,72,224,72,227,72,229,72],"svg",{"viewBox":67,"width":68,"role":69,"ariaLabel":70,"style":71},"0 0 760 244","100%","img","Comparison of native scheduler.yield, a minimal setTimeout fallback and the scheduler-polyfill package across behaviour, size and priority support.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[74,75],"rect",{"className":76,"x":78,"y":78,"width":68,"height":68,"fill":79},[77],"svg-canvas","0","#ffffff",[81,82,83],"title",{},"Native, fallback and polyfill compared",[85,86,70],"desc",{},[74,88],{"x":89,"y":89,"width":90,"height":91,"rx":92,"fill":93,"stroke":94,"style":95},"1","758","242","10","none","currentColor","stroke-opacity:0.18",[97,98,83],"text",{"x":99,"y":100,"fill":94,"style":101},"28.0","34.0","font-size:16px;font-weight:700",[74,103],{"x":99,"y":104,"width":105,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},"56.0","210.0","30.0","fill-opacity:0.06;stroke-opacity:0.4",[97,109,114],{"x":110,"y":111,"fill":94,"style":112,"textAnchor":113},"38.0","75.5","font-size:12.5px;font-weight:700","start","Aspect",[74,116],{"x":117,"y":104,"width":118,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},"238.0","164.7",[97,120,123],{"x":121,"y":111,"fill":94,"style":112,"textAnchor":122},"320.3","middle","Native",[74,125],{"x":126,"y":104,"width":118,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},"402.7",[97,128,130],{"x":129,"y":111,"fill":94,"style":112,"textAnchor":122},"485.0","Inline fallback",[74,132],{"x":133,"y":104,"width":118,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},"567.3",[97,135,43],{"x":136,"y":111,"fill":94,"style":112,"textAnchor":122},"649.7",[74,138],{"x":99,"y":139,"width":105,"height":106,"rx":78,"fill":93,"stroke":94,"style":140},"86.0","stroke-opacity:0.35",[97,142,144],{"x":110,"y":143,"fill":94,"style":112,"textAnchor":113},"105.5","Yields to input and paint",[74,146],{"x":117,"y":139,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[97,150,152],{"x":121,"y":143,"fill":94,"style":151,"textAnchor":122},"font-size:12px","yes",[74,154],{"x":126,"y":139,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},[97,156,152],{"x":129,"y":143,"fill":94,"style":151,"textAnchor":122},[74,158],{"x":133,"y":139,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},[97,160,152],{"x":136,"y":143,"fill":94,"style":151,"textAnchor":122},[74,162],{"x":99,"y":163,"width":105,"height":164,"rx":78,"fill":93,"stroke":94,"style":140},"116.0","46.0",[97,166,168],{"x":110,"y":167,"fill":94,"style":112,"textAnchor":113},"135.5","Continuation runs before other",[97,170,172],{"x":110,"y":171,"fill":94,"style":112,"textAnchor":113},"151.5","tasks",[74,174],{"x":117,"y":163,"width":118,"height":164,"rx":78,"fill":147,"stroke":147,"style":148},[97,176,152],{"x":121,"y":177,"fill":94,"style":151,"textAnchor":122},"143.5",[74,179],{"x":126,"y":163,"width":118,"height":164,"rx":78,"fill":180,"stroke":181,"style":182},"#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[97,184,185],{"x":129,"y":177,"fill":94,"style":151,"textAnchor":122},"no",[74,187],{"x":133,"y":163,"width":118,"height":164,"rx":78,"fill":180,"stroke":181,"style":182},[97,189,190],{"x":136,"y":177,"fill":94,"style":151,"textAnchor":122},"no (emulated)",[74,192],{"x":99,"y":193,"width":105,"height":106,"rx":78,"fill":93,"stroke":94,"style":140},"162.0",[97,195,197],{"x":110,"y":196,"fill":94,"style":112,"textAnchor":113},"181.5","postTask priorities",[74,199],{"x":117,"y":193,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},[97,201,152],{"x":121,"y":196,"fill":94,"style":151,"textAnchor":122},[74,203],{"x":126,"y":193,"width":118,"height":106,"rx":78,"fill":180,"stroke":181,"style":182},[97,205,185],{"x":129,"y":196,"fill":94,"style":151,"textAnchor":122},[74,207],{"x":133,"y":193,"width":118,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},[97,209,210],{"x":136,"y":196,"fill":94,"style":151,"textAnchor":122},"emulated",[74,212],{"x":99,"y":213,"width":105,"height":106,"rx":78,"fill":93,"stroke":94,"style":140},"192.0",[97,215,217],{"x":110,"y":216,"fill":94,"style":112,"textAnchor":113},"211.5","Bytes added",[74,219],{"x":117,"y":213,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},[97,221,78],{"x":121,"y":216,"fill":94,"style":151,"textAnchor":122},[74,223],{"x":126,"y":213,"width":118,"height":106,"rx":78,"fill":147,"stroke":147,"style":148},[97,225,226],{"x":129,"y":216,"fill":94,"style":151,"textAnchor":122},"~100 B",[74,228],{"x":133,"y":213,"width":118,"height":106,"rx":78,"fill":94,"stroke":94,"style":107},[97,230,231],{"x":136,"y":216,"fill":94,"style":151,"textAnchor":122},"~4 KB",[233,234,236],"h2",{"id":235},"rapid-diagnosis","Rapid Diagnosis",[238,239,240,252,261,275],"ul",{},[241,242,243,247,248,251],"li",{},[244,245,246],"strong",{},"Check field support."," Segment RUM by browser: what share of sessions lack ",[30,249,250],{},"scheduler.yield","? If it is Safari-heavy, the fallback path is the main path for many users.",[241,253,254,72,257,260],{},[244,255,256],{},"Search for unguarded calls.",[30,258,259],{},"grep -rn \"scheduler.yield()\" src\u002F"," — every call must go through a guarded helper.",[241,262,263,266,267,270,271,274],{},[244,264,265],{},"Look for TypeError reports."," Error monitoring showing ",[30,268,269],{},"scheduler is not defined"," or ",[30,272,273],{},"scheduler.yield is not a function"," means a guard is missing.",[241,276,277,280],{},[244,278,279],{},"Measure throughput on the fallback."," On Safari, time a chunked job end-to-end; if it is dramatically slower than on Chromium, your yields are too frequent for a timer-based fallback.",[233,282,284],{"id":283},"root-cause-analysis","Root Cause Analysis",[15,286,287,290,291,293],{},[244,288,289],{},"1. Unguarded API usage."," Code written and tested in Chrome calls ",[30,292,32],{}," directly.",[15,295,296,299,300,303],{},[244,297,298],{},"2. Timer clamping in the fallback."," Nested ",[30,301,302],{},"setTimeout"," calls are clamped to a minimum of roughly 4ms after several levels. A fallback that yields every millisecond of work spends most of its time waiting.",[15,305,306,309,310,312],{},[244,307,308],{},"3. Starvation in the fallback."," A ",[30,311,302],{}," continuation goes to the back of the task queue; on a busy page other tasks run first, so chunked jobs take longer to complete.",[15,314,315,318],{},[244,316,317],{},"4. Polyfill loaded late."," A polyfill bundled into a lazy chunk is not present when early code runs, so the guard falls through to a slower path — or the call throws.",[15,320,321],{},[65,322,72,325,72,328,72,331,72,333,72,336,72,338,72,341,72,347,72,353,72,356,72,360,72,363,72,367,72,371,72,375,72,379,72,382,72,385,72,388,72,391,72,394,72,397,72,400,72,403,72,410,72,416,72,420,72,424,72,428,72,432,72,436,72,440,72,444,72],{"viewBox":323,"width":68,"role":69,"ariaLabel":324,"style":71},"0 0 760 178","Timelines showing a native yield resuming the job before a queued third-party task, while a setTimeout fallback lets the third-party task run first.",[74,326],{"className":327,"x":78,"y":78,"width":68,"height":68,"fill":79},[77],[81,329,330],{},"Continuation order: native vs fallback",[85,332,324],{},[74,334],{"x":89,"y":89,"width":90,"height":335,"rx":92,"fill":93,"stroke":94,"style":95},"176",[97,337,330],{"x":99,"y":100,"fill":94,"style":101},[97,339,123],{"x":99,"y":340,"fill":94,"style":112},"74.0",[74,342],{"x":343,"y":104,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"93.8","125.1","26.0","3",[97,348,352],{"x":349,"y":350,"fill":94,"style":351,"textAnchor":122},"156.4","73.5","font-size:11.5px","chunk 1",[74,354],{"x":355,"y":104,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"226.7",[97,357,359],{"x":358,"y":350,"fill":94,"style":351,"textAnchor":122},"289.3","chunk 2",[74,361],{"x":362,"y":104,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"359.7",[97,364,366],{"x":365,"y":350,"fill":94,"style":351,"textAnchor":122},"422.2","chunk 3",[74,368],{"x":369,"y":104,"width":370,"height":345,"rx":346,"fill":94,"stroke":94,"style":107},"492.6","156.7",[97,372,374],{"x":373,"y":350,"fill":94,"style":351,"textAnchor":122},"570.9","vendor task",[97,376,378],{"x":99,"y":377,"fill":94,"style":112},"112.0","Fallback",[74,380],{"x":343,"y":381,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"94.0",[97,383,352],{"x":349,"y":384,"fill":94,"style":351,"textAnchor":122},"111.5",[74,386],{"x":387,"y":381,"width":370,"height":345,"rx":346,"fill":94,"stroke":94,"style":107},"233.1",[97,389,374],{"x":390,"y":384,"fill":94,"style":351,"textAnchor":122},"311.4",[74,392],{"x":393,"y":381,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"404.0",[97,395,359],{"x":396,"y":384,"fill":94,"style":351,"textAnchor":122},"466.5",[74,398],{"x":399,"y":381,"width":344,"height":345,"rx":346,"fill":147,"stroke":147,"style":148},"543.2",[97,401,366],{"x":402,"y":384,"fill":94,"style":351,"textAnchor":122},"605.7",[404,405],"line",{"x1":406,"y1":407,"x2":408,"y2":407,"stroke":94,"style":409},"93.1","134.0","726.0","stroke-opacity:0.4",[97,411,415],{"x":412,"y":413,"fill":414,"style":351,"textAnchor":122},"103.8","151.0","#51617a","0ms",[97,417,419],{"x":418,"y":413,"fill":414,"style":351,"textAnchor":122},"172.2","25ms",[97,421,423],{"x":422,"y":413,"fill":414,"style":351,"textAnchor":122},"251.3","50ms",[97,425,427],{"x":426,"y":413,"fill":414,"style":351,"textAnchor":122},"330.4","75ms",[97,429,431],{"x":430,"y":413,"fill":414,"style":351,"textAnchor":122},"409.5","100ms",[97,433,435],{"x":434,"y":413,"fill":414,"style":351,"textAnchor":122},"488.7","125ms",[97,437,439],{"x":438,"y":413,"fill":414,"style":351,"textAnchor":122},"567.8","150ms",[97,441,443],{"x":442,"y":413,"fill":414,"style":351,"textAnchor":122},"646.9","175ms",[97,445,447],{"x":446,"y":413,"fill":414,"style":351,"textAnchor":122},"715.3","200ms",[233,449,451],{"id":450},"step-by-step-resolution","Step-by-Step Resolution",[453,454,456],"h3",{"id":455},"_1-route-every-yield-through-one-guarded-helper","1. Route every yield through one guarded helper",[458,459,464],"pre",{"className":460,"code":461,"language":462,"meta":463,"style":463},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F yield.js — the only place that references scheduler.yield.\nexport function yieldToMain() {\n  if (globalThis.scheduler?.yield) return scheduler.yield();\n  return new Promise((resolve) => {\n    const { port1, port2 } = new MessageChannel();     \u002F\u002F avoids nested-timeout clamping\n    port1.onmessage = () => { port1.close(); resolve(); };\n    port2.postMessage(null);\n  });\n}\n\u002F\u002F trade-off: a MessageChannel task is not clamped like nested setTimeout, but\n\u002F\u002F it is still an ordinary task — it can be overtaken by other queued work and\n\u002F\u002F has no priority. It is the best fallback without a full scheduler emulation.\n","javascript","",[30,465,466,474,492,512,541,575,606,624,630,636,642,648],{"__ignoreMap":463},[467,468,470],"span",{"class":404,"line":469},1,[467,471,473],{"class":472},"sjfSM","\u002F\u002F yield.js — the only place that references scheduler.yield.\n",[467,475,477,481,484,488],{"class":404,"line":476},2,[467,478,480],{"class":479},"sPARh","export",[467,482,483],{"class":479}," function",[467,485,487],{"class":486},"smZ65"," yieldToMain",[467,489,491],{"class":490},"saISM","() {\n",[467,493,495,498,501,504,507,509],{"class":404,"line":494},3,[467,496,497],{"class":479},"  if",[467,499,500],{"class":490}," (globalThis.scheduler?.yield) ",[467,502,503],{"class":479},"return",[467,505,506],{"class":490}," scheduler.",[467,508,55],{"class":486},[467,510,511],{"class":490},"();\n",[467,513,515,518,521,525,528,532,535,538],{"class":404,"line":514},4,[467,516,517],{"class":479},"  return",[467,519,520],{"class":479}," new",[467,522,524],{"class":523},"sPXB4"," Promise",[467,526,527],{"class":490},"((",[467,529,531],{"class":530},"sQw3B","resolve",[467,533,534],{"class":490},") ",[467,536,537],{"class":479},"=>",[467,539,540],{"class":490}," {\n",[467,542,544,547,550,553,555,558,561,564,566,569,572],{"class":404,"line":543},5,[467,545,546],{"class":479},"    const",[467,548,549],{"class":490}," { ",[467,551,552],{"class":523},"port1",[467,554,48],{"class":490},[467,556,557],{"class":523},"port2",[467,559,560],{"class":490}," } ",[467,562,563],{"class":479},"=",[467,565,520],{"class":479},[467,567,568],{"class":486}," MessageChannel",[467,570,571],{"class":490},"();     ",[467,573,574],{"class":472},"\u002F\u002F avoids nested-timeout clamping\n",[467,576,578,581,584,587,590,592,595,598,601,603],{"class":404,"line":577},6,[467,579,580],{"class":490},"    port1.",[467,582,583],{"class":486},"onmessage",[467,585,586],{"class":479}," =",[467,588,589],{"class":490}," () ",[467,591,537],{"class":479},[467,593,594],{"class":490}," { port1.",[467,596,597],{"class":486},"close",[467,599,600],{"class":490},"(); ",[467,602,531],{"class":486},[467,604,605],{"class":490},"(); };\n",[467,607,609,612,615,618,621],{"class":404,"line":608},7,[467,610,611],{"class":490},"    port2.",[467,613,614],{"class":486},"postMessage",[467,616,617],{"class":490},"(",[467,619,620],{"class":523},"null",[467,622,623],{"class":490},");\n",[467,625,627],{"class":404,"line":626},8,[467,628,629],{"class":490},"  });\n",[467,631,633],{"class":404,"line":632},9,[467,634,635],{"class":490},"}\n",[467,637,639],{"class":404,"line":638},10,[467,640,641],{"class":472},"\u002F\u002F trade-off: a MessageChannel task is not clamped like nested setTimeout, but\n",[467,643,645],{"class":404,"line":644},11,[467,646,647],{"class":472},"\u002F\u002F it is still an ordinary task — it can be overtaken by other queued work and\n",[467,649,651],{"class":404,"line":650},12,[467,652,653],{"class":472},"\u002F\u002F has no priority. It is the best fallback without a full scheduler emulation.\n",[15,655,656,657,659],{},"Expected outcome: no ",[30,658,36],{},"s anywhere, and fallback yields that are not slowed by timer clamping.",[453,661,663],{"id":662},"_2-yield-on-a-time-budget-not-per-item","2. Yield on a time budget, not per item",[458,665,667],{"className":460,"code":666,"language":462,"meta":463,"style":463},"export async function eachWithYield(items, fn, budgetMs = 40) {\n  let deadline = performance.now() + budgetMs;\n  for (const item of items) {\n    fn(item);\n    if (performance.now() >= deadline) { await yieldToMain(); deadline = performance.now() + budgetMs; }\n  }\n}\n\u002F\u002F trade-off: a 40ms budget lets an interaction wait up to 40ms for the current\n\u002F\u002F chunk. On latency-critical screens drop to 20-25ms, accepting more yields.\n",[30,668,669,704,729,749,757,796,801,805,810],{"__ignoreMap":463},[467,670,671,673,676,678,681,683,686,688,691,693,696,698,701],{"class":404,"line":469},[467,672,480],{"class":479},[467,674,675],{"class":479}," async",[467,677,483],{"class":479},[467,679,680],{"class":486}," eachWithYield",[467,682,617],{"class":490},[467,684,685],{"class":530},"items",[467,687,48],{"class":490},[467,689,690],{"class":530},"fn",[467,692,48],{"class":490},[467,694,695],{"class":530},"budgetMs",[467,697,586],{"class":479},[467,699,700],{"class":523}," 40",[467,702,703],{"class":490},") {\n",[467,705,706,709,712,714,717,720,723,726],{"class":404,"line":476},[467,707,708],{"class":479},"  let",[467,710,711],{"class":490}," deadline ",[467,713,563],{"class":479},[467,715,716],{"class":490}," performance.",[467,718,719],{"class":486},"now",[467,721,722],{"class":490},"() ",[467,724,725],{"class":479},"+",[467,727,728],{"class":490}," budgetMs;\n",[467,730,731,734,737,740,743,746],{"class":404,"line":494},[467,732,733],{"class":479},"  for",[467,735,736],{"class":490}," (",[467,738,739],{"class":479},"const",[467,741,742],{"class":523}," item",[467,744,745],{"class":479}," of",[467,747,748],{"class":490}," items) {\n",[467,750,751,754],{"class":404,"line":514},[467,752,753],{"class":486},"    fn",[467,755,756],{"class":490},"(item);\n",[467,758,759,762,765,767,769,772,775,778,780,783,785,787,789,791,793],{"class":404,"line":543},[467,760,761],{"class":479},"    if",[467,763,764],{"class":490}," (performance.",[467,766,719],{"class":486},[467,768,722],{"class":490},[467,770,771],{"class":479},">=",[467,773,774],{"class":490}," deadline) { ",[467,776,777],{"class":479},"await",[467,779,487],{"class":486},[467,781,782],{"class":490},"(); deadline ",[467,784,563],{"class":479},[467,786,716],{"class":490},[467,788,719],{"class":486},[467,790,722],{"class":490},[467,792,725],{"class":479},[467,794,795],{"class":490}," budgetMs; }\n",[467,797,798],{"class":404,"line":577},[467,799,800],{"class":490},"  }\n",[467,802,803],{"class":404,"line":608},[467,804,635],{"class":490},[467,806,807],{"class":404,"line":626},[467,808,809],{"class":472},"\u002F\u002F trade-off: a 40ms budget lets an interaction wait up to 40ms for the current\n",[467,811,812],{"class":404,"line":632},[467,813,814],{"class":472},"\u002F\u002F chunk. On latency-critical screens drop to 20-25ms, accepting more yields.\n",[15,816,817],{},"Expected outcome: the number of yields is bounded by time, so the fallback's per-yield overhead stays a small fraction of total runtime.",[453,819,821],{"id":820},"_3-add-the-full-polyfill-only-if-you-use-priorities","3. Add the full polyfill only if you use priorities",[15,823,824,825,828,829,831,832,834],{},"If your code uses ",[30,826,827],{},"scheduler.postTask"," with priorities or ",[30,830,51],{},", load ",[30,833,43],{}," early — in the entry chunk — so it is installed before any scheduling happens.",[458,836,838],{"className":460,"code":837,"language":462,"meta":463,"style":463},"\u002F\u002F main.js — first import in the entry point.\nimport 'scheduler-polyfill';     \u002F\u002F no-op where the native API exists\n\u002F\u002F trade-off: ~4KB of JavaScript on the critical path for every user, including\n\u002F\u002F those whose browsers already support the API. If you only need yield(), the\n\u002F\u002F inline helper above is enough and costs almost nothing.\n",[30,839,840,845,860,865,870],{"__ignoreMap":463},[467,841,842],{"class":404,"line":469},[467,843,844],{"class":472},"\u002F\u002F main.js — first import in the entry point.\n",[467,846,847,850,854,857],{"class":404,"line":476},[467,848,849],{"class":479},"import",[467,851,853],{"class":852},"sZ8jY"," 'scheduler-polyfill'",[467,855,856],{"class":490},";     ",[467,858,859],{"class":472},"\u002F\u002F no-op where the native API exists\n",[467,861,862],{"class":404,"line":494},[467,863,864],{"class":472},"\u002F\u002F trade-off: ~4KB of JavaScript on the critical path for every user, including\n",[467,866,867],{"class":404,"line":514},[467,868,869],{"class":472},"\u002F\u002F those whose browsers already support the API. If you only need yield(), the\n",[467,871,872],{"class":404,"line":543},[467,873,874],{"class":472},"\u002F\u002F inline helper above is enough and costs almost nothing.\n",[15,876,877],{},"Expected outcome: a uniform API across browsers; priorities are emulated with a JavaScript queue where native support is missing.",[453,879,881],{"id":880},"_4-test-the-fallback-path-explicitly","4. Test the fallback path explicitly",[15,883,884],{},"Force the fallback in tests by deleting the native API before your code runs, and run your INP-sensitive interactions in WebKit (via Playwright) as part of CI.",[458,886,888],{"className":460,"code":887,"language":462,"meta":463,"style":463},"\u002F\u002F playwright: run critical interaction tests in WebKit, where yield is absent.\ntest.use({ browserName: 'webkit' });\ntest('filters stay responsive without native scheduler.yield', async ({ page }) => {\n  await page.goto('\u002Fcatalogue');\n  await page.click('#apply-filters');\n  await expect(page.locator('.result-card').first()).toBeVisible({ timeout: 2000 });\n});\n\u002F\u002F trade-off: WebKit in CI is not Safari on an iPhone — CPU and timer behaviour\n\u002F\u002F differ. Use it to catch breakage, not to measure Safari performance.\n",[30,889,890,895,912,940,958,974,1012,1017,1022],{"__ignoreMap":463},[467,891,892],{"class":404,"line":469},[467,893,894],{"class":472},"\u002F\u002F playwright: run critical interaction tests in WebKit, where yield is absent.\n",[467,896,897,900,903,906,909],{"class":404,"line":476},[467,898,899],{"class":490},"test.",[467,901,902],{"class":486},"use",[467,904,905],{"class":490},"({ browserName: ",[467,907,908],{"class":852},"'webkit'",[467,910,911],{"class":490}," });\n",[467,913,914,917,919,922,924,927,930,933,936,938],{"class":404,"line":494},[467,915,916],{"class":486},"test",[467,918,617],{"class":490},[467,920,921],{"class":852},"'filters stay responsive without native scheduler.yield'",[467,923,48],{"class":490},[467,925,926],{"class":479},"async",[467,928,929],{"class":490}," ({ ",[467,931,932],{"class":530},"page",[467,934,935],{"class":490}," }) ",[467,937,537],{"class":479},[467,939,540],{"class":490},[467,941,942,945,948,951,953,956],{"class":404,"line":514},[467,943,944],{"class":479},"  await",[467,946,947],{"class":490}," page.",[467,949,950],{"class":486},"goto",[467,952,617],{"class":490},[467,954,955],{"class":852},"'\u002Fcatalogue'",[467,957,623],{"class":490},[467,959,960,962,964,967,969,972],{"class":404,"line":543},[467,961,944],{"class":479},[467,963,947],{"class":490},[467,965,966],{"class":486},"click",[467,968,617],{"class":490},[467,970,971],{"class":852},"'#apply-filters'",[467,973,623],{"class":490},[467,975,976,978,981,984,987,989,992,995,998,1001,1004,1007,1010],{"class":404,"line":577},[467,977,944],{"class":479},[467,979,980],{"class":486}," expect",[467,982,983],{"class":490},"(page.",[467,985,986],{"class":486},"locator",[467,988,617],{"class":490},[467,990,991],{"class":852},"'.result-card'",[467,993,994],{"class":490},").",[467,996,997],{"class":486},"first",[467,999,1000],{"class":490},"()).",[467,1002,1003],{"class":486},"toBeVisible",[467,1005,1006],{"class":490},"({ timeout: ",[467,1008,1009],{"class":523},"2000",[467,1011,911],{"class":490},[467,1013,1014],{"class":404,"line":608},[467,1015,1016],{"class":490},"});\n",[467,1018,1019],{"class":404,"line":626},[467,1020,1021],{"class":472},"\u002F\u002F trade-off: WebKit in CI is not Safari on an iPhone — CPU and timer behaviour\n",[467,1023,1024],{"class":404,"line":632},[467,1025,1026],{"class":472},"\u002F\u002F differ. Use it to catch breakage, not to measure Safari performance.\n",[15,1028,1029],{},"Expected outcome: regressions in the fallback path are caught before release.",[15,1031,1032],{},[65,1033,72,1036,72,1039,72,1042,72,1044,72,1061,72,1064,72,1066,72,1071,72,1077,72,1080,72,1085,72,1092,72,1097,72,1101,72,1105,72,1108,72,1112,72,1114,72,1117,72,1120,72,1123,72,1125,72,1128,72,1131,72,1135,72,1137,72,1140,72,1143,72,1146,72,1150,72,1153,72,1156,72],{"viewBox":1034,"width":68,"role":69,"ariaLabel":1035,"style":71},"0 0 760 325","Decision sequence for choosing between no polyfill, an inline yield helper and the full scheduler polyfill.",[74,1037],{"className":1038,"x":78,"y":78,"width":68,"height":68,"fill":79},[77],[81,1040,1041],{},"Which fallback do you need?",[85,1043,1035],{},[1045,1046,1047],"defs",{},[1048,1049,1056],"marker",{"id":1050,"viewBox":1051,"refX":1052,"refY":1053,"markerWidth":1054,"markerHeight":1054,"orient":1055},"fa501e8421","0 0 10 10","9","5","7","auto-start-reverse",[1057,1058],"path",{"d":1059,"fill":94,"style":1060},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[74,1062],{"x":89,"y":89,"width":90,"height":1063,"rx":92,"fill":93,"stroke":94,"style":95},"323",[97,1065,1041],{"x":99,"y":100,"fill":94,"style":101},[74,1067],{"x":99,"y":104,"width":1068,"height":1069,"rx":1070,"fill":94,"stroke":94,"style":107},"320.0","35.0","6",[97,1072,1076],{"x":1073,"y":1074,"fill":94,"style":1075,"textAnchor":122},"188.0","78.0","font-size:13px;font-weight:700","Only Chromium and Firefox users?",[74,1078],{"x":1079,"y":104,"width":1068,"height":1069,"rx":1070,"fill":147,"stroke":147,"style":148},"412.0",[97,1081,1084],{"x":1082,"y":1074,"fill":94,"style":1083,"textAnchor":122},"572.0","font-size:12.5px","Native API with a guard is enough",[404,1086],{"x1":1087,"y1":350,"x2":1088,"y2":350,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},"348.0","410.0","1.5","stroke-opacity:0.6","url(#fa501e8421)",[97,1093,152],{"x":1094,"y":1095,"fill":147,"style":1096,"textAnchor":122},"380.0","66.5","font-size:11.5px;font-weight:700",[404,1098],{"x1":1073,"y1":1099,"x2":1073,"y2":1100,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},"91.0","125.0",[97,1102,185],{"x":1103,"y":1104,"fill":414,"style":1096},"196.0","113.0",[74,1106],{"x":99,"y":1107,"width":1068,"height":1069,"rx":1070,"fill":94,"stroke":94,"style":107},"127.0",[97,1109,1111],{"x":1073,"y":1110,"fill":94,"style":1075,"textAnchor":122},"149.0","Using postTask priorities or TaskController?",[74,1113],{"x":1079,"y":1107,"width":1068,"height":1069,"rx":1070,"fill":147,"stroke":147,"style":148},[97,1115,1116],{"x":1082,"y":1110,"fill":94,"style":1083,"textAnchor":122},"Load scheduler-polyfill in the entry chunk",[404,1118],{"x1":1087,"y1":1119,"x2":1088,"y2":1119,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},"144.5",[97,1121,152],{"x":1094,"y":1122,"fill":147,"style":1096,"textAnchor":122},"137.5",[404,1124],{"x1":1073,"y1":193,"x2":1073,"y2":1103,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},[97,1126,185],{"x":1103,"y":1127,"fill":414,"style":1096},"184.0",[74,1129],{"x":99,"y":1130,"width":1068,"height":1069,"rx":1070,"fill":94,"stroke":94,"style":107},"198.0",[97,1132,1134],{"x":1073,"y":1133,"fill":94,"style":1075,"textAnchor":122},"220.0","Only need yield()?",[74,1136],{"x":1079,"y":1130,"width":1068,"height":1069,"rx":1070,"fill":147,"stroke":147,"style":148},[97,1138,1139],{"x":1082,"y":1133,"fill":94,"style":1083,"textAnchor":122},"Inline MessageChannel helper (~100 bytes)",[404,1141],{"x1":1087,"y1":1142,"x2":1088,"y2":1142,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},"215.5",[97,1144,152],{"x":1094,"y":1145,"fill":147,"style":1096,"textAnchor":122},"208.5",[404,1147],{"x1":1073,"y1":1148,"x2":1073,"y2":1149,"stroke":94,"strokeWidth":1089,"style":1090,"markerEnd":1091},"233.0","267.0",[97,1151,185],{"x":1103,"y":1152,"fill":414,"style":1096},"255.0",[74,1154],{"x":99,"y":1155,"width":1068,"height":100,"rx":1070,"fill":180,"stroke":181,"style":182},"269.0",[97,1157,1159],{"x":1073,"y":1158,"fill":94,"style":1083,"textAnchor":122},"290.5","Inline helper — the smallest safe option",[233,1161,1163],{"id":1162},"verification","Verification",[15,1165,1166,1167,1169],{},"Run the chunked job in Chromium and WebKit and compare total duration and longest task. Both should keep every task under ",[30,1168,423],{},"; WebKit's total may be modestly longer because continuations can be overtaken. If WebKit's total is several times longer, reduce yield frequency or check for clamping. In RUM, split INP by browser: the improvement from yielding should appear in all engines, even if Chromium benefits most.",[233,1171,1173],{"id":1172},"what-the-native-api-gives-you-that-a-polyfill-cannot","What the Native API Gives You That a Polyfill Cannot",[15,1175,1176,1177,1179,1180,1183],{},"The defining feature of ",[30,1178,32],{}," is that its continuation is scheduled ahead of other tasks of the same priority that were queued while you yielded. That requires the browser's task scheduler to know about your continuation — something no JavaScript library can arrange, because from the page's point of view the only primitives available are ordinary tasks. A polyfill can provide the API shape, the priorities between ",[58,1181,1182],{},"your"," tasks, and cancellation; it cannot reorder your work relative to a third-party script's timers. In practice this matters most on pages with heavy third-party activity, where fallback continuations can wait behind vendor tasks. The pragmatic response is to reduce that vendor activity during interactions rather than to seek a more elaborate polyfill.",[233,1185,1187],{"id":1186},"rollout-checklist","Rollout Checklist",[15,1189,1190,1191,1193,1194,1197,1198,1200],{},"Before shipping yield-based chunking to all browsers, confirm four things. Every call goes through the guarded helper, verified by a lint rule banning direct ",[30,1192,250],{}," references outside it. The time budget per chunk is set per job, not globally, so latency-critical paths can use a smaller one. Error monitoring has an alert for ",[30,1195,1196],{},"scheduler","-related ",[30,1199,36],{},"s, which would indicate a missed guard in a lazily loaded chunk. And RUM segments INP by browser engine, so you can confirm the improvement holds on Safari, where the fallback is the only path and where, for many consumer sites, a third or more of mobile traffic comes from.",[233,1202,1204],{"id":1203},"faq","FAQ",[1206,1207,1210,1214],"details",{"className":1208},[1209],"faq-item",[1211,1212,1213],"summary",{},"Why not use requestAnimationFrame as the fallback?",[15,1215,1216,1217,270,1220,1222],{},"A rAF callback runs before the next paint, so awaiting it yields until the next frame — up to 16ms on a 60Hz display even if the browser is otherwise idle, and not at all in background tabs. For chunked work this wastes time and stalls entirely when the tab is hidden. A ",[30,1218,1219],{},"MessageChannel",[30,1221,302],{}," task is more appropriate.",[1206,1224,1226,1229],{"className":1225},[1209],[1211,1227,1228],{},"Is it safe to monkey-patch window.scheduler with my fallback?",[15,1230,1231,1232,1234],{},"It works, but it can confuse third-party code that feature-detects the API and expects real priorities, and it can mask the absence of native support in your own diagnostics. A local helper is cleaner. Install a global polyfill only if you need ",[30,1233,47],{}," semantics everywhere, including in libraries.",[1206,1236,1238,1241],{"className":1237},[1209],[1211,1239,1240],{},"Does yielding inside a microtask chain work?",[15,1242,1243,1244,1247,1248,270,1251,1254],{},"Yes — ",[30,1245,1246],{},"await yieldToMain()"," ends the current task and resumes in a new one, regardless of how deep in a promise chain you are. What does not work is \"yielding\" with ",[30,1249,1250],{},"await Promise.resolve()",[30,1252,1253],{},"queueMicrotask",", which stay in the same task and give the browser no chance to handle input or paint.",[233,1256,1258],{"id":1257},"related","Related",[238,1260,1261,1268,1275],{},[241,1262,1263,1267],{},[19,1264,1266],{"href":1265},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fscheduler-yield-vs-settimeout-0-for-yielding\u002F","scheduler.yield() vs setTimeout(0) for yielding"," — the behavioural difference in depth.",[241,1269,1270,1274],{},[19,1271,1273],{"href":1272},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fusing-scheduler-posttask-priorities\u002F","Using scheduler.postTask() priorities"," — when you need the full polyfill.",[241,1276,1277,1281],{},[19,1278,1280],{"href":1279},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fpolyfills-and-differential-serving\u002Ftargeting-baseline-browsers-with-esbuild-and-swc\u002F","Targeting baseline browsers with esbuild and SWC"," — keeping other polyfills off the critical path.",[1283,1284,1286],"script",{"type":1285},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Polyfill scheduler.yield() Across Browsers Without Losing Its Benefits\",\n  \"description\": \"How to use scheduler.yield() everywhere — a minimal fallback, the scheduler-polyfill package, and which behaviours a polyfill cannot reproduce.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Route every yield through one guarded helper\",\n      \"text\": \"Expected outcome: no TypeErrors anywhere, and fallback yields that are not slowed by timer clamping.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Yield on a time budget, not per item\",\n      \"text\": \"Expected outcome: the number of yields is bounded by time, so the fallback's per-yield overhead stays a small fraction of total runtime.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Add the full polyfill only if you use priorities\",\n      \"text\": \"If your code uses scheduler.postTask with priorities or TaskController, load scheduler-polyfill early — in the entry chunk — so it is installed before any scheduling happens.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Test the fallback path explicitly\",\n      \"text\": \"Force the fallback in tests by deleting the native API before your code runs, and run your INP-sensitive interactions in WebKit (via Playwright) as part of CI.\"\n    }\n  ]\n}\n",[1283,1288,1289],{"type":1285},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Polyfill scheduler.yield() Across Browsers Without Losing Its Benefits\",\n  \"description\": \"How to use scheduler.yield() everywhere — a minimal fallback, the scheduler-polyfill package, and which behaviours a polyfill cannot reproduce.\",\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\u002Fpolyfilling-scheduler-yield-across-browsers\u002F\"\n  }\n}\n",[1283,1291,1292],{"type":1285},"\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\": \"Polyfilling scheduler.yield() Across Browsers\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers\u002F\"\n    }\n  ]\n}\n",[1294,1295,1296],"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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}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);}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}",{"title":463,"searchDepth":476,"depth":476,"links":1298},[1299,1300,1301,1307,1308,1309,1310,1311],{"id":235,"depth":476,"text":236},{"id":283,"depth":476,"text":284},{"id":450,"depth":476,"text":451,"children":1302},[1303,1304,1305,1306],{"id":455,"depth":494,"text":456},{"id":662,"depth":494,"text":663},{"id":820,"depth":494,"text":821},{"id":880,"depth":494,"text":881},{"id":1162,"depth":476,"text":1163},{"id":1172,"depth":476,"text":1173},{"id":1186,"depth":476,"text":1187},{"id":1203,"depth":476,"text":1204},{"id":1257,"depth":476,"text":1258},"How to use scheduler.yield() everywhere — a minimal fallback, the scheduler-polyfill package, and which behaviours a polyfill cannot reproduce.","md",{"slug":1315,"type":1316,"breadcrumb":1317,"datePublished":1325,"dateModified":1325},"polyfilling-scheduler-yield-across-browsers","article",[1318,1321,1322,1323],{"name":1319,"url":1320},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1324},"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers",{"title":1329,"description":1330},"Polyfilling scheduler.yield() Across All Browsers","scheduler.yield() is missing in Safari and older engines. Compare a tiny inline fallback with a full polyfill, and avoid the pitfalls that make yielding slower.","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fpolyfilling-scheduler-yield-across-browsers\u002Findex","oGPM_-2BfdDPPBLQ9papyxEFWKtPNEchiYqAVJhPVuo",[1334,1338],{"title":1335,"path":1336,"stem":1337},"Breaking Up Long Tasks in Vue Event Handlers","\u002Fcore-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-vue-event-handlers","core-web-vitals-measurement\u002Foptimizing-inp-with-scheduler-yield\u002Fbreaking-up-long-tasks-in-vue-event-handlers\u002Findex",{"title":1339,"path":1340,"stem":1341},"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",1791308078634]