[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-queuemicrotask-microtask-vs-macrotask":93,"path-membership-queuemicrotask-microtask-vs-macrotask":289,"related-queuemicrotask-microtask-vs-macrotask":290,"code:js:true:b51t4z":420,"code:js:true:18o9d65":421,"code:js:true:18fqxe":422,"code:js:true:1ipgnui":423,"comments-01a1a52e-1213-71ac-8458-b4edff92b4c2":424},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",174,{"slug":50,"name":51,"articles":52},"javascript","Javascript",141,{"slug":54,"name":55,"articles":56},"frontend","Frontend",96,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",71,{"slug":62,"name":63,"articles":64},"css","Css",51,{"slug":66,"name":67,"articles":68},"performance","Performance",30,{"slug":70,"name":71,"articles":72},"typescript","Typescript",25,{"slug":74,"name":75,"articles":76},"react","React",20,{"slug":78,"name":79,"articles":80},"programming","Programming",19,{"slug":82,"name":83,"articles":84},"browser","Browser",18,{"slug":86,"name":87,"articles":88},"node","Node",16,{"slug":90,"name":91,"articles":92},"html","Html",12,{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":102,"likeCount":19,"commentCount":19,"author":103,"vertical":108,"topic":109,"tags":111,"_count":116,"playground":118,"body":120,"bodyMd":272,"seo":273,"translationGroupId":276,"series":97,"podcastUrl":97,"verticalId":5,"thread":277,"assessments":279,"translations":285,"quiz":287},"01a1a52e-1213-71ac-8458-b4edff92b4c2","queuemicrotask-microtask-vs-macrotask","Your `setTimeout(fn, 0)` Doesn't Run Next. `queueMicrotask` Does.",null,"setTimeout(fn, 0) feels like 'run this right after the current code.' It isn't — every pending promise cuts in line first. queueMicrotask puts your callback in that same fast lane, on purpose.","\u002Fmedia\u002Fcovers\u002Fqueuemicrotask-microtask-vs-macrotask.png",5,"2026-11-10T14:37:38.531Z",8,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":110},"JavaScript",[112,113,114,115],{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":86,"name":87,"color":97},{"slug":78,"name":79,"color":97},{"assessments":117},1,{"slug":95,"title":119},"queueMicrotask vs setTimeout — the race",{"blocks":121,"version":117},[122,126,129,132,136,139,145,148,152,155,158,161,167,170,173,177,180,183,186,189,192,195,199,202,206,209,212,215,218,221,224,227,230,233,236,239,242,245,248,254,257,260,263,266],{"id":123,"html":124,"type":125},"b1","\u003Cp>The test called the function, then did \u003Ccode>await Promise.resolve()\u003C\u002Fcode>, then checked the state. It failed. Not flaky — failed every single run, for this one function and no other.\u003C\u002Fp>","paragraph",{"id":127,"html":128,"type":125},"b2","\u003Cp>The function wasn&#39;t broken. It deferred its work with \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode>, and \u003Ccode>await Promise.resolve()\u003C\u002Fcode> doesn&#39;t wait for that. It can&#39;t. By the time \u003Ccode>setTimeout\u003C\u002Fcode> even gets a turn, that \u003Ccode>await\u003C\u002Fcode> is long finished.\u003C\u002Fp>",{"id":130,"html":131,"type":125},"b3","\u003Cp>The fix was a one-word swap. Understanding why it mattered took longer.\u003C\u002Fp>",{"id":133,"html":134,"text":134,"type":135,"level":31},"b4","The batcher that looked obviously right","heading",{"id":137,"html":138,"type":125},"b5","\u003Cp>Here&#39;s the function in question — a tiny update batcher for a settings panel. Call it five times in a row and it should write once, not five times:\u003C\u002Fp>",{"id":140,"code":141,"type":142,"language":143,"highlight":144},"b6","let pending = new Map();\nlet scheduled = false;\n\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    setTimeout(flush, 0); \u002F\u002F \"run this once, right after the current burst of calls\"\n  }\n}\n\nfunction flush() {\n  scheduled = false;\n  applyUpdates(pending);\n  pending.clear();\n}","code","js",[],{"id":146,"html":147,"type":125},"b7","\u003Cp>Click a few toggles fast, and \u003Ccode>flush\u003C\u002Fcode> runs once with the final values. Manual testing looks perfect. Then someone writes the obvious test:\u003C\u002Fp>",{"id":149,"code":150,"type":142,"language":143,"highlight":151},"b8","test(\"coalesces rapid updates into one flush\", async () => {\n  queueUpdate(\"theme\", \"dark\");\n  queueUpdate(\"theme\", \"darker\");\n\n  await Promise.resolve(); \u002F\u002F \"let things settle\"\n\n  expect(getStoreValue(\"theme\")).toBe(\"darker\"); \u002F\u002F fails — still unset\n});",[],{"id":153,"html":154,"type":125},"b9","\u003Cp>That \u003Ccode>await Promise.resolve()\u003C\u002Fcode> is a common idiom for &quot;let the microtask queue drain before I assert.&quot; It isn&#39;t broken, and it isn&#39;t a flake you can retry away. \u003Ccode>flush\u003C\u002Fcode> genuinely has not run yet when the assertion checks — because \u003Ccode>setTimeout\u003C\u002Fcode>, even at \u003Ccode>0\u003C\u002Fcode>, schedules into a queue that a microtask-flushing \u003Ccode>await\u003C\u002Fcode> never visits.\u003C\u002Fp>",{"id":156,"html":157,"text":157,"type":135,"level":31},"b10","Two queues, and one of them always finishes first",{"id":159,"html":160,"type":125},"b11","\u003Cp>Every JavaScript engine actually runs two separate queues, and they are not interchangeable:\u003C\u002Fp>",{"id":162,"type":163,"items":164,"ordered":18},"b12","list",[165,166],"\u003Cstrong>The microtask queue\u003C\u002Fstrong> — where \u003Ccode>Promise.resolve().then(fn)\u003C\u002Fcode> and \u003Ccode>queueMicrotask(fn)\u003C\u002Fcode> both land, in the same queue.","\u003Cstrong>The macrotask (task) queue\u003C\u002Fstrong> — where \u003Ccode>setTimeout\u003C\u002Fcode>, UI events, and I\u002FO callbacks land.",{"id":168,"html":169,"type":125},"b13","\u003Cp>The HTML spec&#39;s event loop rule is blunt about the order: once the current script finishes, the engine drains the \u003Cem>entire\u003C\u002Fem> microtask queue — including any new microtasks queued while draining — before it&#39;s allowed to touch the next macrotask, or render a frame. This isn&#39;t &quot;promises happen to be fast.&quot; It&#39;s a hard ordering guarantee, and every major engine (V8, SpiderMonkey, JavaScriptCore) implements it the same way.\u003C\u002Fp>",{"id":171,"html":172,"type":125},"b14","\u003Cp>The classic proof:\u003C\u002Fp>",{"id":174,"code":175,"type":142,"language":143,"highlight":176},"b15","console.log(\"1 — sync\");\n\nsetTimeout(() => console.log(\"4 — setTimeout\"), 0);\n\nPromise.resolve().then(() => console.log(\"3 — microtask\"));\n\nconsole.log(\"2 — sync\");\n\n\u002F\u002F always: 1, 2, 3, 4",[],{"id":178,"html":179,"type":125},"b16","\u003Cp>That part you may already know. Here&#39;s the part that surprises people: it&#39;s not a head start, it&#39;s a \u003Cem>standing rule\u003C\u002Fem>. Chain ten \u003Ccode>.then()\u003C\u002Fcode> calls after that same \u003Ccode>setTimeout\u003C\u002Fcode>, each one scheduling the next only once the previous resolves, and all ten still finish before the timeout fires — because the microtask queue keeps draining as long as anything is in it, and a \u003Ccode>.then()\u003C\u002Fcode> callback adds its continuation back onto that same queue. The \u003Ccode>setTimeout\u003C\u002Fcode> doesn&#39;t get a turn until the queue is empty, however long that takes.\u003C\u002Fp>",{"id":181,"html":182,"type":125},"b17","\u003C!-- playground:start -->",{"id":184,"html":185,"text":185,"type":135,"level":31},"b18","🎮 Try it yourself",{"id":187,"html":188,"type":125},"b19","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":125},"b20","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":125},"b21","\u003C!-- playground:end -->",{"id":196,"html":197,"text":198,"type":135,"level":31},"b22","The fix: schedule into the queue you&#39;re actually being checked against","The fix: schedule into the queue you're actually being checked against",{"id":200,"html":201,"type":125},"b23","\u003Cp>Swap the one line:\u003C\u002Fp>",{"id":203,"code":204,"type":142,"language":143,"highlight":205},"b24","function queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    queueMicrotask(flush); \u002F\u002F same queue as every .then() and every await\n  }\n}",[],{"id":207,"html":208,"type":125},"b25","\u003Cp>\u003Ccode>queueMicrotask\u003C\u002Fcode> still coalesces every synchronous call in the current burst into a single \u003Ccode>flush\u003C\u002Fcode> — that part of the behavior doesn&#39;t change, because the microtask checkpoint only runs once the call stack is empty either way. What changes is \u003Cem>which\u003C\u002Fem> queue \u003Ccode>flush\u003C\u002Fcode> sits in. Now it&#39;s the same queue \u003Ccode>await Promise.resolve()\u003C\u002Fcode> empties. The test passes, because \u003Ccode>flush\u003C\u002Fcode> — queued first — runs before the test&#39;s own \u003Ccode>await\u003C\u002Fcode> gets its turn, not after.\u003C\u002Fp>",{"id":210,"html":211,"type":125},"b26","\u003Cp>\u003Ccode>queueMicrotask\u003C\u002Fcode> isn&#39;t exotic: it&#39;s Baseline, widely supported since 2020 in every major browser, and it&#39;s been a global in Node since v11. It takes a callback, returns nothing, and that&#39;s the entire API.\u003C\u002Fp>",{"id":213,"html":214,"text":214,"type":135,"level":31},"b27","Where setTimeout is still the right call",{"id":216,"html":217,"type":125},"b28","\u003Cp>Don&#39;t read this as &quot;replace every \u003Ccode>setTimeout(0)\u003C\u002Fcode>.&quot; Microtasks are not free — the queue has to fully drain before the browser can render a frame or respond to a click, so a microtask that keeps re-queuing itself can freeze the page solid. If what you actually need is to let the browser breathe — paint a frame, handle an input event — between chunks of work, you want a macrotask-shaped yield (\u003Ccode>setTimeout\u003C\u002Fcode>, or \u003Ccode>scheduler.yield\u003C\u002Fcode> where it&#39;s available), not a microtask. \u003Ccode>queueMicrotask\u003C\u002Fcode> answers &quot;run this before anything else gets a turn.&quot; It is the wrong tool for &quot;give the user a moment.&quot;\u003C\u002Fp>",{"id":219,"html":220,"type":125},"b29","\u003Cp>One more gotcha worth knowing if you touch Node: \u003Ccode>process.nextTick()\u003C\u002Fcode> jumps the line even ahead of \u003Ccode>queueMicrotask\u003C\u002Fcode> — Node fully drains its \u003Ccode>nextTick\u003C\u002Fcode> queue before the promise\u002Fmicrotask queue on every turn. If you&#39;re writing isomorphic code, \u003Ccode>queueMicrotask\u003C\u002Fcode> is the one that behaves the same in the browser and in Node; \u003Ccode>process.nextTick\u003C\u002Fcode> doesn&#39;t exist in the browser at all.\u003C\u002Fp>",{"id":222,"html":223,"type":125},"b30","\u003Cp>The test wasn&#39;t wrong, and neither was the original instinct to defer the flush. \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> was just answering a different question than the one being asked — &quot;run after the next paint,&quot; instead of &quot;run after absolutely everything currently promised, and not one tick later.&quot; \u003Ccode>queueMicrotask\u003C\u002Fcode> is the second sentence, spelled correctly.\u003C\u002Fp>",{"id":225,"html":226,"type":125},"b31","\u003C!-- quiz:start -->",{"id":228,"html":229,"text":229,"type":135,"level":31},"b32","🧠 Test yourself",{"id":231,"html":232,"type":125},"b33","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":234,"html":235,"type":125},"b34","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":237,"html":238,"type":125},"b35","\u003C!-- quiz:end -->",{"id":240,"html":241,"type":125},"b36","\u003Cp>Where else in your code is a \u003Ccode>setTimeout(fn, 0)\u003C\u002Fcode> quietly promising &quot;right after&quot; but delivering &quot;eventually&quot;? I&#39;d bet there&#39;s at least one test file that already knows.\u003C\u002Fp>",{"id":243,"html":244,"type":125},"b37","\u003C!-- related:start -->",{"id":246,"html":247,"text":247,"type":135,"level":31},"b38","📚 Read next",{"id":249,"type":163,"items":250,"ordered":18},"b39",[251,252,253],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\">My .catch() Never Ran. The Bug Was Two Lines Above It\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffetch-retry-exponential-backoff\">Stop Retrying Immediately. Exponential Backoff Fixes It.\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestanimationframe-scroll-throttling\">You&#39;re Debouncing Scroll Wrong. requestAnimationFrame Fixes It.\u003C\u002Fa>",{"id":255,"html":256,"type":125},"b40","\u003C!-- related:end -->",{"id":258,"type":259},"b41","divider",{"id":261,"html":262,"type":125},"b42","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":264,"html":265,"type":125},"b43","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":267,"type":163,"items":268,"ordered":18},"b44",[269,270,271],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","The test called the function, then did `await Promise.resolve()`, then checked the state. It failed. Not flaky — failed every single run, for this one function and no other.\n\nThe function wasn't broken. It deferred its work with `setTimeout(fn, 0)`, and `await Promise.resolve()` doesn't wait for that. It can't. By the time `setTimeout` even gets a turn, that `await` is long finished.\n\nThe fix was a one-word swap. Understanding why it mattered took longer.\n\n## The batcher that looked obviously right\n\nHere's the function in question — a tiny update batcher for a settings panel. Call it five times in a row and it should write once, not five times:\n\n```js\nlet pending = new Map();\nlet scheduled = false;\n\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    setTimeout(flush, 0); \u002F\u002F \"run this once, right after the current burst of calls\"\n  }\n}\n\nfunction flush() {\n  scheduled = false;\n  applyUpdates(pending);\n  pending.clear();\n}\n```\n\nClick a few toggles fast, and `flush` runs once with the final values. Manual testing looks perfect. Then someone writes the obvious test:\n\n```js\ntest(\"coalesces rapid updates into one flush\", async () => {\n  queueUpdate(\"theme\", \"dark\");\n  queueUpdate(\"theme\", \"darker\");\n\n  await Promise.resolve(); \u002F\u002F \"let things settle\"\n\n  expect(getStoreValue(\"theme\")).toBe(\"darker\"); \u002F\u002F fails — still unset\n});\n```\n\nThat `await Promise.resolve()` is a common idiom for \"let the microtask queue drain before I assert.\" It isn't broken, and it isn't a flake you can retry away. `flush` genuinely has not run yet when the assertion checks — because `setTimeout`, even at `0`, schedules into a queue that a microtask-flushing `await` never visits.\n\n## Two queues, and one of them always finishes first\n\nEvery JavaScript engine actually runs two separate queues, and they are not interchangeable:\n\n- **The microtask queue** — where `Promise.resolve().then(fn)` and `queueMicrotask(fn)` both land, in the same queue.\n- **The macrotask (task) queue** — where `setTimeout`, UI events, and I\u002FO callbacks land.\n\nThe HTML spec's event loop rule is blunt about the order: once the current script finishes, the engine drains the *entire* microtask queue — including any new microtasks queued while draining — before it's allowed to touch the next macrotask, or render a frame. This isn't \"promises happen to be fast.\" It's a hard ordering guarantee, and every major engine (V8, SpiderMonkey, JavaScriptCore) implements it the same way.\n\nThe classic proof:\n\n```js\nconsole.log(\"1 — sync\");\n\nsetTimeout(() => console.log(\"4 — setTimeout\"), 0);\n\nPromise.resolve().then(() => console.log(\"3 — microtask\"));\n\nconsole.log(\"2 — sync\");\n\n\u002F\u002F always: 1, 2, 3, 4\n```\n\nThat part you may already know. Here's the part that surprises people: it's not a head start, it's a *standing rule*. Chain ten `.then()` calls after that same `setTimeout`, each one scheduling the next only once the previous resolves, and all ten still finish before the timeout fires — because the microtask queue keeps draining as long as anything is in it, and a `.then()` callback adds its continuation back onto that same queue. The `setTimeout` doesn't get a turn until the queue is empty, however long that takes.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The fix: schedule into the queue you're actually being checked against\n\nSwap the one line:\n\n```js\nfunction queueUpdate(key, value) {\n  pending.set(key, value);\n  if (!scheduled) {\n    scheduled = true;\n    queueMicrotask(flush); \u002F\u002F same queue as every .then() and every await\n  }\n}\n```\n\n`queueMicrotask` still coalesces every synchronous call in the current burst into a single `flush` — that part of the behavior doesn't change, because the microtask checkpoint only runs once the call stack is empty either way. What changes is *which* queue `flush` sits in. Now it's the same queue `await Promise.resolve()` empties. The test passes, because `flush` — queued first — runs before the test's own `await` gets its turn, not after.\n\n`queueMicrotask` isn't exotic: it's Baseline, widely supported since 2020 in every major browser, and it's been a global in Node since v11. It takes a callback, returns nothing, and that's the entire API.\n\n## Where setTimeout is still the right call\n\nDon't read this as \"replace every `setTimeout(0)`.\" Microtasks are not free — the queue has to fully drain before the browser can render a frame or respond to a click, so a microtask that keeps re-queuing itself can freeze the page solid. If what you actually need is to let the browser breathe — paint a frame, handle an input event — between chunks of work, you want a macrotask-shaped yield (`setTimeout`, or `scheduler.yield` where it's available), not a microtask. `queueMicrotask` answers \"run this before anything else gets a turn.\" It is the wrong tool for \"give the user a moment.\"\n\nOne more gotcha worth knowing if you touch Node: `process.nextTick()` jumps the line even ahead of `queueMicrotask` — Node fully drains its `nextTick` queue before the promise\u002Fmicrotask queue on every turn. If you're writing isomorphic code, `queueMicrotask` is the one that behaves the same in the browser and in Node; `process.nextTick` doesn't exist in the browser at all.\n\nThe test wasn't wrong, and neither was the original instinct to defer the flush. `setTimeout(fn, 0)` was just answering a different question than the one being asked — \"run after the next paint,\" instead of \"run after absolutely everything currently promised, and not one tick later.\" `queueMicrotask` is the second sentence, spelled correctly.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nWhere else in your code is a `setTimeout(fn, 0)` quietly promising \"right after\" but delivering \"eventually\"? I'd bet there's at least one test file that already knows.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [My .catch() Never Ran. The Bug Was Two Lines Above It](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety)\n- [Stop Retrying Immediately. Exponential Backoff Fixes It.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ffetch-retry-exponential-backoff)\n- [You're Debouncing Scroll Wrong. requestAnimationFrame Fixes It.](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Frequestanimationframe-scroll-throttling)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":96,"canonical":274,"description":275},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fqueuemicrotask-microtask-vs-macrotask","setTimeout(fn, 0) feels like 'run this right after the current code.' It isn't — every pending promise cuts in line first. queueMicrotask puts your callback in that same fast lane,","01a1a52e-1213-71ac-8458-b921b0a52f8b",{"id":278,"locked":18},"01a1a52e-1246-7799-86bc-a1be70bcad9a",[280],{"id":281,"slug":95,"title":282,"_count":283},"01a1a52e-126b-7259-8b0a-7ba01c0dcea4","queueMicrotask vs setTimeout: test yourself",{"questions":284},7,[286],{"locale":13,"slug":95},{"id":281,"slug":95,"title":282,"_count":288,"questionCount":284},{"questions":284},[],{"items":291,"meta":418},[292,304,325,345,364,382,400],{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":100,"publishedAt":101,"viewCount":293,"likeCount":19,"commentCount":19,"author":294,"vertical":295,"topic":296,"tags":297,"_count":302,"playground":303,"hasQuiz":17,"hasPlayground":17},9,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":110},[298,299,300,301],{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":86,"name":87,"color":97},{"slug":78,"name":79,"color":97},{"assessments":117},{"slug":95},{"id":305,"slug":306,"title":307,"subtitle":97,"excerpt":308,"coverUrl":309,"locale":13,"readingMinutes":310,"publishedAt":311,"viewCount":312,"likeCount":19,"commentCount":19,"author":313,"vertical":314,"topic":315,"tags":318,"_count":323,"playground":324,"hasQuiz":17,"hasPlayground":17},"01a16761-e187-74dc-bd78-ecaa659b3254","vue-weekly-teleport-defer-target-timing","Vue Teleport defer: The Target-Timing Bug Behind a Dozen Patches","Vue's Teleport defer prop took a dozen follow-up fixes after 3.5 — the target-timing bug, the Suspense interaction, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-teleport-defer-target-timing.png",14,"2026-11-09T15:43:15.151Z",62,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":316,"name":317},"vue","Vue",[319,320,321,322],{"slug":316,"name":317,"color":97},{"slug":50,"name":51,"color":97},{"slug":58,"name":59,"color":97},{"slug":46,"name":47,"color":97},{"assessments":117},{"slug":306},{"id":326,"slug":327,"title":328,"subtitle":97,"excerpt":329,"coverUrl":330,"locale":13,"readingMinutes":331,"publishedAt":332,"viewCount":333,"likeCount":19,"commentCount":19,"author":334,"vertical":335,"topic":336,"tags":338,"_count":343,"playground":344,"hasQuiz":17,"hasPlayground":17},"01a1a007-b7cf-743d-a21f-35adebf60420","prefers-color-scheme-theme-flash","Stop Blaming `prefers-color-scheme` For Your Theme Flash","Your dark mode toggle flashes light for a frame on every reload — but prefers-color-scheme alone never does that. The real bug is where your theme script runs relative to the browser's first paint.","\u002Fmedia\u002Fcovers\u002Fprefers-color-scheme-theme-flash.png",6,"2026-11-09T15:42:44.790Z",73,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":62,"name":337},"CSS",[339,340,341,342],{"slug":62,"name":63,"color":97},{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":54,"name":55,"color":97},{"assessments":117},{"slug":327},{"id":346,"slug":347,"title":348,"subtitle":97,"excerpt":349,"coverUrl":350,"locale":13,"readingMinutes":100,"publishedAt":351,"viewCount":352,"likeCount":19,"commentCount":19,"author":353,"vertical":354,"topic":355,"tags":357,"_count":362,"playground":363,"hasQuiz":17,"hasPlayground":17},"01a19abd-c743-729e-8f0e-b3cb1dcc0ef5","commandfor-invoker-commands-api","Stop Writing JavaScript to Open Dialogs. `commandfor` Fixes It.","Every \u003Cdialog> and popover in your app still needs an addEventListener just to open and close — commandfor and the Invoker Commands API let the browser do it with zero JavaScript.","\u002Fmedia\u002Fcovers\u002Fcommandfor-invoker-commands-api.png","2026-11-08T18:35:26.332Z",117,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":90,"name":356},"HTML",[358,359,360,361],{"slug":46,"name":47,"color":97},{"slug":90,"name":91,"color":97},{"slug":50,"name":51,"color":97},{"slug":54,"name":55,"color":97},{"assessments":117},{"slug":347},{"id":365,"slug":366,"title":367,"subtitle":97,"excerpt":368,"coverUrl":369,"locale":13,"readingMinutes":331,"publishedAt":370,"viewCount":371,"likeCount":19,"commentCount":19,"author":372,"vertical":373,"topic":374,"tags":375,"_count":380,"playground":381,"hasQuiz":17,"hasPlayground":17},"01a1972e-eaa8-706f-bb0e-2c61b0faeef0","fetch-race-condition-stale-responses","Stop Shipping Search Boxes With a Race Condition","Type fast into a search box wired to fetch(), and the result on screen isn't from your last keystroke — it's from whichever request happened to finish last. That's a race condition, and the fix is a one-line guard.","\u002Fmedia\u002Fcovers\u002Ffetch-race-condition-stale-responses.png","2026-11-07T06:21:08.821Z",181,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":110},[376,377,378,379],{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":66,"name":67,"color":97},{"slug":78,"name":79,"color":97},{"assessments":117},{"slug":366},{"id":383,"slug":384,"title":385,"subtitle":97,"excerpt":386,"coverUrl":387,"locale":13,"readingMinutes":100,"publishedAt":388,"viewCount":389,"likeCount":19,"commentCount":19,"author":390,"vertical":391,"topic":392,"tags":393,"_count":398,"playground":399,"hasQuiz":17,"hasPlayground":17},"01a1972e-e328-75e5-bf48-38fd66ed3892","passive-event-listeners-scroll-performance","You're Blocking Scroll by Default. `passive: true` Fixes It.","Every touch and wheel listener you add can make the browser wait before it scrolls — because it has to check whether you'll call preventDefault(). `passive: true` tells it you won't, and scrolling stops waiting on your code.","\u002Fmedia\u002Fcovers\u002Fpassive-event-listeners-scroll-performance.png","2026-11-06T06:10:55.120Z",204,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":110},[394,395,396,397],{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":66,"name":67,"color":97},{"slug":82,"name":83,"color":97},{"assessments":117},{"slug":384},{"id":401,"slug":402,"title":403,"subtitle":97,"excerpt":404,"coverUrl":405,"locale":13,"readingMinutes":100,"publishedAt":406,"viewCount":407,"likeCount":19,"commentCount":19,"author":408,"vertical":409,"topic":410,"tags":411,"_count":416,"playground":417,"hasQuiz":17,"hasPlayground":17},"01a18b4e-fbba-7593-896a-8808e32d35cc","uint8array-tobase64","Stop Hacking Base64 With btoa(). toBase64() Fixes It Natively.","btoa() throws on emoji, and the classic String.fromCharCode(...bytes) workaround crashes on large files. toBase64() and fromBase64() on Uint8Array fix both — no string round trip, no library.","\u002Fmedia\u002Fcovers\u002Fuint8array-tobase64.png","2026-11-05T06:10:25.995Z",246,{"id":104,"name":105,"username":106,"avatarUrl":97,"headline":107},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":50,"name":110},[412,413,414,415],{"slug":50,"name":51,"color":97},{"slug":46,"name":47,"color":97},{"slug":82,"name":83,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":402},{"page":117,"perPage":284,"total":52,"totalPages":419},21,"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> pending \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Map\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> scheduled \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">queueUpdate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>key\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  pending\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>key\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>scheduled\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    scheduled \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>flush\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F \"run this once, right after the current burst of calls\"\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">flush\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  scheduled \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">applyUpdates\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>pending\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  pending\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">clear\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-func\">test\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"coalesces rapid updates into one flush\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">queueUpdate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"theme\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"dark\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-func\">queueUpdate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"theme\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"darker\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F \"let things settle\"\n\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">expect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getStoreValue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"theme\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toBe\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"darker\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F fails — still unset\n\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"1 — sync\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-func\">setTimeout\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"4 — setTimeout\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">resolve\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"3 — microtask\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\nconsole\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">\"2 — sync\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F always: 1, 2, 3, 4\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">queueUpdate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>key\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  pending\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">set\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>key\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">!\u003C\u002Fspan>scheduled\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    scheduled \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>;\n    \u003Cspan class=\"shj-func\">queueMicrotask\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>flush\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F same queue as every .then() and every await\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":425},[]]