[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-vue-weekly-reactivity-batch-watcher-throws":44,"search-suggestions":447,"path-membership-vue-weekly-reactivity-batch-watcher-throws":488,"related-vue-weekly-reactivity-batch-watcher-throws":489,"code:js:true:z55y2j":594,"code:js:true:1j5foyw":595,"code:js:true:t0xmrb":596,"code:js:true:1g8mypl":597,"code:js:true:u63zyb":598,"code:js:true:1g19wh4":599,"code:js:true:u1tb1z":600,"code:js:true:f5sd72":601,"comments-01a0f781-ba1b-7528-8bc8-b9c9fe1b0e38":602},[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,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":74,"playground":76,"body":78,"bodyMd":399,"seo":400,"translationGroupId":402,"series":403,"podcastUrl":48,"verticalId":5,"thread":435,"assessments":437,"translations":443,"quiz":445},"01a0f781-ba1b-7528-8bc8-b9c9fe1b0e38","vue-weekly-reactivity-batch-watcher-throws","Vue 3.6: When a Watcher Throws, Your Other Watchers Stall",null,"A Vue 3.6 reactivity regression let a throwing watcher silently stall its siblings. The exact flush() mechanism, the fix, and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-reactivity-batch-watcher-throws.png",15,"2026-10-19T06:10:59.563Z",33,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"vue","Vue",[64,65,68,71],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},"javascript","Javascript",{"slug":69,"name":70,"color":48},"tutorial","Tutorial",{"slug":72,"name":73,"color":48},"webdev","Webdev",{"assessments":75},1,{"slug":46,"title":77},"Vue 3.6 notify buffer &amp; flush() — interactive playground",{"blocks":79,"version":75},[80,84,89,92,101,104,107,110,121,124,128,131,137,140,144,147,150,154,157,160,164,167,170,173,176,179,182,185,189,192,195,199,202,205,209,212,215,218,222,225,228,231,239,242,249,252,255,258,262,265,268,271,275,278,281,284,287,318,322,325,328,331,334,337,340,348,351,354,357,360,363,366,369,372,375,381,384,387,390,393],{"id":81,"html":82,"type":83},"b1","\u003Cp>Two watchers, same \u003Ccode>ref\u003C\u002Fcode>, nothing to do with each other. One of them has a bug and throws. The other one — which has no bug at all — just stops updating. Not forever. It comes back to life the next time some \u003Cem>completely unrelated\u003C\u002Fem> ref changes somewhere else in your app. If you&#39;ve ever stared at a watcher that &quot;randomly&quot; seemed to miss an update and blamed your own code, this is the kind of thing that can actually cause it — and across every Vue 3.6 pre-release — from the first alpha through the last rc before the fix — it genuinely did.\u003C\u002Fp>","paragraph",{"id":85,"html":86,"text":87,"type":88,"level":31},"b2","What You&#39;ll Learn","What You'll Learn","heading",{"id":90,"html":91,"type":83},"b3","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":93,"type":94,"items":95,"ordered":18},"b4","list",[96,97,98,99,100],"Explain what a reactive &quot;batch&quot; is and why Vue groups effect notifications together instead of firing them one at a time","Read Vue&#39;s actual \u003Ccode>flush()\u003C\u002Fcode> loop and predict what happens when one effect in it throws","Name the exact regression that shipped in Vue 3.6.0-rc.9, the GitHub issue that caught it, and the fix that landed in rc.10","Tell the difference between this reactivity-level batch and the separate DOM-update queue \u003Ccode>nextTick\u003C\u002Fcode> flushes","Write watcher and computed code that doesn&#39;t depend on Vue isolating your failures for you",{"id":102,"html":103,"text":103,"type":88,"level":31},"b5","Who This Is For",{"id":105,"html":106,"type":83},"b6","\u003Cp>You&#39;ve written \u003Ccode>ref\u003C\u002Fcode>, \u003Ccode>watch\u003C\u002Fcode>, and \u003Ccode>computed\u003C\u002Fcode> in Vue 3 with \u003Ccode>&lt;script setup&gt;\u003C\u002Fcode> and you know the basics of dependency tracking. This article assumes that; if read-time tracking itself — how Vue even knows which ref a computed depends on — is still fuzzy, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij\">Vue Reactivity: ref vs reactive\u003C\u002Fa> builds that foundation first. Useful background, not required reading.\u003C\u002Fp>",{"id":108,"html":109,"text":109,"type":88,"level":31},"b7","Table of Contents",{"id":111,"type":94,"items":112,"ordered":18},"b8",[113,114,115,116,117,118,119,120],"\u003Ca href=\"#the-problem-a-watcher-that-randomly-stops-firing\">The Problem: A Watcher That &quot;Randomly&quot; Stops Firing\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-writes-dont-notify-they-queue-then-flush\">The Mental Model: Writes Don&#39;t Notify — They Queue, Then Flush\u003C\u002Fa>","\u003Ca href=\"#building-it-up-inside-flush\">Building It Up: Inside flush()\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge Cases and Gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best Practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat Sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key Takeaways\u003C\u002Fa>",{"id":122,"html":123,"type":83},"b9","\u003Cp>This is written against \u003Cstrong>Vue 3.5.x\u003C\u002Fstrong> (the current stable line on npm) and \u003Cstrong>Vue 3.6.0-rc.10\u003C\u002Fstrong> (verified September 2026), the release candidate for Vue&#39;s rewritten, \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals\">alien-signals\u003C\u002Fa>-based reactivity core. The regression this article walks through was never in any stable 3.5.x release — 3.5 doesn&#39;t even run this code, since the alien-signals-based rewrite only exists on the 3.6 line. But it&#39;s not a one-RC blip either: the unprotected \u003Ccode>flush()\u003C\u002Fcode> loop shipped as-is from the very first 3.6.0-alpha.1, through every beta and every release candidate up to and including rc.9, and was only fixed in rc.10. Everything about the underlying batch-and-flush mechanism, though, is how Vue&#39;s reactivity has worked since well before 3.6; it&#39;s the error-handling around it, specifically, that was missing for most of 3.6&#39;s pre-release life.\u003C\u002Fp>",{"id":125,"html":126,"text":127,"type":88,"level":31},"b10","The Problem: A Watcher That &quot;Randomly&quot; Stops Firing","The Problem: A Watcher That \"Randomly\" Stops Firing",{"id":129,"html":130,"type":83},"b11","\u003Cp>Say you have two independent \u003Ccode>watch\u003C\u002Fcode> callbacks reading the same source — maybe one updates a UI label, the other logs to an analytics queue:\u003C\u002Fp>",{"id":132,"code":133,"type":134,"language":135,"highlight":136},"b12","import { ref, computed, watch } from 'vue'\n\nconst user = ref({ name: 'Ava', age: 29 })\n\nconst label = computed(() => {\n  \u002F\u002F a bug: throws if `name` is ever missing\n  return user.value.name.toUpperCase()\n})\n\nwatch(label, (v) => console.log('[ui] label →', v), { flush: 'sync' })\nwatch(user, (v) => console.log('[analytics] user changed →', v.age), { flush: 'sync', deep: true })\n\nconst unrelated = ref(0)","code","js",[],{"id":138,"html":139,"type":83},"b13","\u003Cp>Both watchers are set to \u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> on purpose, so you can reason about the exact order things run in — no scheduler, no microtask, just &quot;the write happens, then the watchers that depend on it run.&quot; Now trigger the bug:\u003C\u002Fp>",{"id":141,"code":142,"type":134,"language":135,"highlight":143},"b14","user.value = { age: 30 } \u002F\u002F no `name` — the computed's getter throws",[],{"id":145,"html":146,"type":83},"b15","\u003Cp>On Vue 3.5, and on Vue 3.6 from rc.10 onward, you get what you&#39;d expect: the \u003Ccode>[ui] label\u003C\u002Fcode> watcher&#39;s getter throws inside \u003Ccode>computed()\u003C\u002Fcode>, the error is reported through Vue&#39;s error handling, and the \u003Ccode>[analytics] user changed\u003C\u002Fcode> watcher — which has nothing to do with \u003Ccode>label\u003C\u002Fcode> and nothing wrong with it — still runs, because \u003Ccode>user\u003C\u002Fcode> changed and it&#39;s watching \u003Ccode>user\u003C\u002Fcode>.\u003C\u002Fp>",{"id":148,"html":149,"type":83},"b16","\u003Cp>On every 3.6 pre-release up to and including rc.9, that second line never appeared. Not because the watcher was broken. Because of \u003Cem>when\u003C\u002Fem> it ran:\u003C\u002Fp>",{"id":151,"code":152,"type":134,"language":135,"highlight":153},"b17","console.clear()\nuser.value = { age: 30 }\n\u002F\u002F [nothing — both watchers were silent, including the one that should be fine]\n\nunrelated.value++\n\u002F\u002F [analytics] user changed → 30   ← shows up now, attached to the WRONG write",[],{"id":155,"html":156,"type":83},"b18","\u003Cp>The analytics watcher&#39;s callback didn&#39;t fire for the write that actually triggered it. It fired later, piggybacking on a completely unrelated \u003Ccode>unrelated.value++\u003C\u002Fcode>. If \u003Ccode>unrelated\u003C\u002Fcode> never changes again, that watcher simply never runs for that update at all. From the outside this looks exactly like &quot;the watcher missed an update&quot; — a bug you&#39;d go looking for in your own code, because nothing in the stack trace points at Vue&#39;s internals.\u003C\u002Fp>",{"id":158,"html":159,"type":83},"b19","\u003Cp>This was filed as \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674\">vuejs\u002Fcore#15674\u003C\u002Fa> and fixed by \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676\">PR #15676\u003C\u002Fa>, which shipped in \u003Cstrong>3.6.0-rc.10\u003C\u002Fstrong>. To understand \u003Cem>why\u003C\u002Fem> a throw in one watcher could ever delay a different watcher, you need the actual mechanism underneath every \u003Ccode>ref\u003C\u002Fcode> write — which is worth knowing regardless of this bug, because it explains ordering questions you&#39;ll hit constantly.\u003C\u002Fp>",{"id":161,"html":162,"text":163,"type":88,"level":31},"b20","The Mental Model: Writes Don&#39;t Notify — They Queue, Then Flush","The Mental Model: Writes Don't Notify — They Queue, Then Flush",{"id":165,"html":166,"type":83},"b21","\u003Cp>Here&#39;s the picture most developers carry around, and it&#39;s wrong in a way that matters: &quot;when I write \u003Ccode>ref.value = x\u003C\u002Fcode>, Vue loops over everything that depends on it and runs each one, one after another, right there.&quot; That&#39;s not what happens.\u003C\u002Fp>",{"id":168,"html":169,"type":83},"b22","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> a write doesn&#39;t call its dependents directly. It marks them \u003Cstrong>dirty\u003C\u002Fstrong> and pushes them into a single, shared \u003Cstrong>notify buffer\u003C\u002Fstrong> — one array, shared across every ref and computed in your whole app for the duration of that write. Once the write itself is done, Vue walks that buffer in a loop called \u003Ccode>flush()\u003C\u002Fcode>, calling \u003Ccode>effect.notify()\u003C\u002Fcode> on each entry in order. Only inside \u003Ccode>notify()\u003C\u002Fcode> does an individual watcher&#39;s callback, or a computed&#39;s recomputation, actually run.\u003C\u002Fp>",{"id":171,"html":172,"type":83},"b23","\u003Cp>That indirection is what makes a &quot;batch&quot; a real, inspectable thing instead of a metaphor. It&#39;s also why two unrelated watchers on two unrelated refs can end up sharing a buffer if their writes happen to be entangled by a computed in between (as \u003Ccode>label\u003C\u002Fcode> and \u003Ccode>user\u003C\u002Fcode> are above, via \u003Ccode>user.value.name\u003C\u002Fcode>) — and it&#39;s exactly the mechanism \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-computed-what-it-caches-and-when-it-reruns-1e03\">\u003Ccode>computed()\u003C\u002Fcode>&#39;s caching\u003C\u002Fa> rides on top of: a computed is dirtied through this same notify path, not recalculated eagerly on every write.\u003C\u002Fp>",{"id":174,"html":175,"type":83},"b24","\u003Cp>This is \u003Cstrong>not\u003C\u002Fstrong> the same queue \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp\">\u003Ccode>nextTick\u003C\u002Fcode> flushes\u003C\u002Fa>. \u003Ccode>nextTick\u003C\u002Fcode>&#39;s job is scheduling \u003Cem>component re-renders\u003C\u002Fem> on a microtask so the DOM updates once per tick instead of once per write. The notify buffer described here sits one layer underneath that: a component&#39;s render job is itself just one more effect that gets pushed into this same buffer and walked by this same \u003Ccode>flush()\u003C\u002Fcode> loop. A \u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> watcher skips the component scheduler entirely and runs straight out of this loop, which is why the examples above use it — it makes the ordering deterministic enough to reason about on paper.\u003C\u002Fp>",{"id":177,"html":178,"text":178,"type":88,"level":31},"b25","Building It Up: Inside flush()",{"id":180,"html":181,"type":83},"b26","\u003Cp>Let&#39;s build the mechanism up from nothing, the same way Vue&#39;s reactivity package does it internally. This is a simplified model of the real algorithm in \u003Ccode>packages\u002Freactivity\u002Fsrc\u002Fsystem.ts\u003C\u002Fcode> — not a toy metaphor, the actual control flow — small enough to read start to finish.\u003C\u002Fp>",{"id":183,"html":184,"type":83},"b27","\u003Cp>\u003Cstrong>Stage 1 — one effect, one write.\u003C\u002Fstrong> No batching needed yet:\u003C\u002Fp>",{"id":186,"code":187,"type":134,"language":135,"highlight":188},"b28","function makeEffect(fn) {\n  return { notify: () => fn() }\n}\n\nconst e1 = makeEffect(() => console.log('effect 1 ran'))\ne1.notify() \u002F\u002F effect 1 ran",[],{"id":190,"html":191,"type":83},"b29","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>notify()\u003C\u002Fcode> is the thing that actually executes a watcher&#39;s callback or re-runs a computed&#39;s getter. Everything from here on is about \u003Cem>when\u003C\u002Fem> and \u003Cem>in what order\u003C\u002Fem> \u003Ccode>notify()\u003C\u002Fcode> gets called across several effects.\u003C\u002Fp>",{"id":193,"html":194,"type":83},"b30","\u003Cp>\u003Cstrong>Stage 2 — a shared buffer and a flush loop.\u003C\u002Fstrong> This is the real shape:\u003C\u002Fp>",{"id":196,"code":197,"type":134,"language":135,"highlight":198},"b31","let notifyBuffer = []\nlet notifyIndex = 0\nlet notifyBufferLength = 0\n\nfunction queue(effect) {\n  notifyBuffer[notifyBufferLength++] = effect\n}\n\nfunction flush() {\n  while (notifyIndex \u003C notifyBufferLength) {\n    const effect = notifyBuffer[notifyIndex]\n    notifyBuffer[notifyIndex++] = undefined\n    effect.notify()\n  }\n  notifyIndex = 0\n  notifyBufferLength = 0\n}",[],{"id":200,"html":201,"type":83},"b32","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> a write queues one or more effects into \u003Ccode>notifyBuffer\u003C\u002Fcode>, then calls \u003Ccode>flush()\u003C\u002Fcode> once. \u003Ccode>flush()\u003C\u002Fcode> walks the whole buffer in a plain loop — every queued effect gets its turn, in the order it was queued, synchronously, before \u003Ccode>flush()\u003C\u002Fcode> returns.\u003C\u002Fp>",{"id":203,"html":204,"type":83},"b33","\u003Cp>\u003Cstrong>Stage 3 — introduce a throwing effect, with no protection.\u003C\u002Fstrong> This is what every Vue 3.6 pre-release actually shipped, from 3.6.0-alpha.1 through 3.6.0-rc.9:\u003C\u002Fp>",{"id":206,"code":207,"type":134,"language":135,"highlight":208},"b34","const good1 = makeEffect(() => console.log('good watcher A ran'))\nconst bad = makeEffect(() => { throw new Error('getter blew up') })\nconst good2 = makeEffect(() => console.log('good watcher B ran'))\n\nqueue(good1)\nqueue(bad)\nqueue(good2)\n\nflush()\n\u002F\u002F good watcher A ran\n\u002F\u002F Uncaught Error: getter blew up\n\u002F\u002F (good watcher B never ran — and notifyIndex\u002FnotifyBufferLength\n\u002F\u002F  were left pointing mid-buffer, non-zero)",[],{"id":210,"html":211,"type":83},"b35","\u003Cp>Walk through exactly what happened: \u003Ccode>good1.notify()\u003C\u002Fcode> runs and returns normally, \u003Ccode>notifyIndex\u003C\u002Fcode> advances to 1. \u003Ccode>bad.notify()\u003C\u002Fcode> throws. Because there&#39;s no \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> around the call, the exception propagates straight out of the \u003Ccode>while\u003C\u002Fcode> loop — skipping the two lines that reset \u003Ccode>notifyIndex\u003C\u002Fcode> and \u003Ccode>notifyBufferLength\u003C\u002Fcode> to 0. \u003Ccode>good2\u003C\u002Fcode> was already sitting in the buffer, but the loop never reaches it. The buffer isn&#39;t empty and the index isn&#39;t reset — it&#39;s just \u003Cem>abandoned\u003C\u002Fem>, mid-walk, with \u003Ccode>good2\u003C\u002Fcode> still inside it.\u003C\u002Fp>",{"id":213,"html":214,"type":83},"b36","\u003Cp>The next time \u003Cstrong>any\u003C\u002Fstrong> write anywhere calls \u003Ccode>flush()\u003C\u002Fcode> again, the \u003Ccode>while (notifyIndex &lt; notifyBufferLength)\u003C\u002Fcode> check is still true from the leftover state, so the loop picks up right where it left off and finally calls \u003Ccode>good2.notify()\u003C\u002Fcode> — attached to whatever unrelated write happened to trigger that next flush. That&#39;s the exact bug from the opening example.\u003C\u002Fp>",{"id":216,"html":217,"type":83},"b37","\u003Cp>\u003Cstrong>Stage 4 — the fix, isolating each notify call.\u003C\u002Fstrong> This is what shipped in 3.6.0-rc.10, restoring 3.5&#39;s behavior:\u003C\u002Fp>",{"id":219,"code":220,"type":134,"language":135,"highlight":221},"b38","function flush() {\n  let hasError = false\n  let error\n  while (notifyIndex \u003C notifyBufferLength) {\n    const effect = notifyBuffer[notifyIndex]\n    notifyBuffer[notifyIndex++] = undefined\n    try {\n      effect.notify()\n    } catch (e) {\n      if (!hasError) {\n        hasError = true\n        error = e\n      }\n    }\n  }\n  notifyIndex = 0\n  notifyBufferLength = 0\n  if (hasError) throw error\n}",[],{"id":223,"html":224,"type":83},"b39","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> a single \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> wraps each individual \u003Ccode>effect.notify()\u003C\u002Fcode> call, so a throw from one effect can&#39;t stop the loop from reaching the next one — the loop itself can no longer throw at all. That&#39;s what makes the reset lines right after the loop unconditional: there&#39;s no uncaught exception left to jump past them, so \u003Ccode>notifyIndex\u003C\u002Fcode> and \u003Ccode>notifyBufferLength\u003C\u002Fcode> always get reset to zero before the next write&#39;s flush. Only the \u003Cem>first\u003C\u002Fem> error that occurred is remembered, in \u003Ccode>hasError\u003C\u002Fcode>\u002F\u003Ccode>error\u003C\u002Fcode>, and rethrown once every queued effect has had its turn.\u003C\u002Fp>",{"id":226,"html":227,"type":83},"b40","\u003Cp>Run the stage-3 example through this version and \u003Ccode>good2 ran\u003C\u002Fcode> prints immediately, in the same flush as the throw — and the error still surfaces to whoever triggered the write, just after everyone&#39;s had their notify.\u003C\u002Fp>",{"id":229,"html":230,"text":230,"type":88,"level":31},"b41","Edge Cases and Gotchas",{"id":232,"type":94,"items":233,"ordered":18},"b42",[234,235,236,237,238],"\u003Cstrong>Two throwing effects in the same batch.\u003C\u002Fstrong> Only the \u003Cem>first\u003C\u002Fem> error is captured and rethrown; the second (and any later) thrower still gets its \u003Ccode>notify()\u003C\u002Fcode> call — so its side effects before the \u003Ccode>throw\u003C\u002Fcode> statement still happen — but its exception is silently discarded. If you need to know about every failure in a batch, don&#39;t rely on the single rethrown error; catch inside each effect instead (see Best Practices).","\u003Cstrong>\u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> isn&#39;t required for this to matter.\u003C\u002Fstrong> The examples use it to make ordering legible on paper, but default (\u003Ccode>&#39;pre&#39;\u003C\u002Fcode>) and \u003Ccode>&#39;post&#39;\u003C\u002Fcode> watchers are notified through this exact same buffer and loop — they&#39;re just queued by a component-level scheduler job that itself sits in the buffer, rather than running immediately. A throw anywhere in that chain goes through the same per-effect \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> logic described above.","\u003Cstrong>This was never a stable-release bug, but it was a long-lived pre-release one.\u003C\u002Fstrong> It&#39;s worth repeating because version confusion is exactly how stale advice spreads in a framework ecosystem: 3.5.x never had it — it predates the alien-signals rewrite entirely — and it never reached a non-RC 3.6.0 release. But it wasn&#39;t confined to rc.9 either: the unprotected \u003Ccode>flush()\u003C\u002Fcode> shipped unchanged from 3.6.0-alpha.1 all the way through 3.6.0-rc.9, and was only fixed in rc.10. If you&#39;re pinned to an exact 3.6 pre-release for testing, pin to rc.10 or later.","\u003Cstrong>\u003Ccode>app.config.errorHandler\u003C\u002Fcode> still fires for the thrown error either way\u003C\u002Fstrong> — this bug was never about error \u003Cem>reporting\u003C\u002Fem> going missing, only about a sibling effect&#39;s \u003Cem>execution\u003C\u002Fem> being delayed. Don&#39;t confuse &quot;I got an error callback&quot; with &quot;every watcher that should have run, ran.&quot;","\u003Cstrong>A computed&#39;s own dirty flag isn&#39;t affected by this.\u003C\u002Fstrong> The computed in the opening example (\u003Ccode>label\u003C\u002Fcode>) correctly stays dirty and will recompute (and rethrow) on its next read regardless of this bug; what the bug delayed was the \u003Cem>other\u003C\u002Fem> watcher, not the throwing one.",{"id":240,"html":241,"text":241,"type":88,"level":31},"b43","Best Practices",{"id":243,"type":94,"items":244,"ordered":18},"b44",[245,246,247,248],"\u003Cstrong>Don&#39;t let a risky computed or watcher getter run unguarded\u003C\u002Fstrong> if a sibling&#39;s correctness depends on timing. Wrap the risky part in its own \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> and fall back to a safe value, rather than letting the exception travel up into Vue&#39;s flush loop at all:\n\n\u003Ccode>js const label = computed(() =&gt; {   try {     return user.value.name.toUpperCase()   } catch {     return &#39;—&#39;   } }) \u003C\u002Fcode>\n","\u003Cstrong>Treat watchers on the same source as independent, not ordered.\u003C\u002Fstrong> Even with the fix, only the first error in a batch is rethrown — you cannot depend on &quot;if watcher A didn&#39;t throw, I&#39;ll find out about watcher B&#39;s error the same way.&quot; Each effect&#39;s failure handling is its own responsibility.","\u003Cstrong>Use \u003Ccode>app.config.errorHandler\u003C\u002Fcode> for centralized reporting\u003C\u002Fstrong>, not as a substitute for per-watcher guards — it tells you \u003Cem>that\u003C\u002Fem> something threw, not which of several effects in the same batch also ran cleanly.","\u003Cstrong>If you&#39;re testing against a 3.6 release candidate today, pin to rc.10 or newer.\u003C\u002Fstrong> \u003Ccode>npm view vue dist-tags\u003C\u002Fcode> tells you the current \u003Ccode>rc\u003C\u002Fcode> tag; don&#39;t trust a cached \u003Ccode>node_modules\u003C\u002Fcode> from a few weeks ago to reflect it.",{"id":250,"html":251,"text":251,"type":88,"level":31},"b45","FAQ",{"id":253,"html":254,"text":254,"type":88,"level":43},"b46","Does this bug affect Vue 3.5?",{"id":256,"html":257,"type":83},"b47","\u003Cp>No. It came in with the alien-signals-based reactivity rewrite that&#39;s exclusive to the 3.6 line — 3.5.x doesn&#39;t run this code at all, and every 3.5.x release behaves like the fixed version described in Stage 4. Within 3.6, the bug wasn&#39;t limited to \u003Ccode>rc.9\u003C\u002Fcode> either: the unprotected \u003Ccode>flush()\u003C\u002Fcode> shipped unchanged from \u003Ccode>3.6.0-alpha.1\u003C\u002Fcode> through \u003Ccode>3.6.0-rc.9\u003C\u002Fcode>, and was fixed in \u003Ccode>3.6.0-rc.10\u003C\u002Fcode>.\u003C\u002Fp>",{"id":259,"html":260,"text":261,"type":88,"level":43},"b48","Is the batch described here the same as the \u003Ccode>nextTick\u003C\u002Fcode> queue?","Is the batch described here the same as the nextTick queue?",{"id":263,"html":264,"type":83},"b49","\u003Cp>No — they&#39;re adjacent, not the same thing. \u003Ccode>nextTick\u003C\u002Fcode> schedules component re-renders on a microtask so the DOM updates once per tick. The notify buffer in this article is the layer underneath: a component&#39;s render job is itself one effect queued into this buffer, alongside your \u003Ccode>watch\u003C\u002Fcode> and \u003Ccode>computed\u003C\u002Fcode> effects. \u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> watchers skip the \u003Ccode>nextTick\u003C\u002Fcode> scheduling and run directly out of this loop.\u003C\u002Fp>",{"id":266,"html":267,"text":267,"type":88,"level":43},"b50","Will my component fail to re-render if a sibling watcher throws?",{"id":269,"html":270,"type":83},"b51","\u003Cp>With the fix (3.6.0-rc.10 and later, and all of 3.5), no — your render job still gets its \u003Ccode>notify()\u003C\u002Fcode> call in the same flush, exactly like \u003Ccode>good2\u003C\u002Fcode> in Stage 4. On any unpatched pre-release (3.6.0-alpha.1 through rc.9), it theoretically could, for the same reason any other queued effect could: a throw earlier in the buffer would abandon the loop before reaching it.\u003C\u002Fp>",{"id":272,"html":273,"text":274,"type":88,"level":43},"b52","Should I stop using \u003Ccode>watch()\u003C\u002Fcode> and handle everything with \u003Ccode>try\u003C\u002Fcode>\u002F\u003Ccode>catch\u003C\u002Fcode> instead?","Should I stop using watch() and handle everything with try\u002Fcatch instead?",{"id":276,"html":277,"type":83},"b53","\u003Cp>No — \u003Ccode>watch()\u003C\u002Fcode> and \u003Ccode>computed()\u003C\u002Fcode> are still the right tool. The takeaway isn&#39;t &quot;avoid the reactivity system,&quot; it&#39;s &quot;don&#39;t assume Vue isolates failures between independent effects for you.&quot; Guard the specific logic that can realistically throw, inside the watcher or computed itself.\u003C\u002Fp>",{"id":279,"html":280,"text":280,"type":88,"level":43},"b54","Where can I read the actual fix?",{"id":282,"html":283,"type":83},"b55","\u003Cp>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674\">vuejs\u002Fcore#15674\u003C\u002Fa> has the original repro and root-cause analysis; \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676\">PR #15676\u003C\u002Fa> has the diff that shipped in 3.6.0-rc.10.\u003C\u002Fp>",{"id":285,"html":286,"text":286,"type":88,"level":31},"b56","Cheat Sheet",{"id":288,"head":289,"rows":292,"type":317},"b57",[290,291],"What","How it behaves",[293,296,299,302,305,308,311,314],[294,295],"A \u003Ccode>ref\u003C\u002Fcode> write","Marks dependent effects dirty, queues them into a shared notify buffer — does \u003Cstrong>not\u003C\u002Fstrong> call them directly",[297,298],"\u003Ccode>flush()\u003C\u002Fcode>","Walks the notify buffer in a loop, calling \u003Ccode>effect.notify()\u003C\u002Fcode> on each queued effect in order",[300,301],"\u003Ccode>flush: &#39;sync&#39;\u003C\u002Fcode> watcher","Runs straight out of this loop, with no \u003Ccode>nextTick\u003C\u002Fcode> scheduling in between",[303,304],"Default (\u003Ccode>&#39;pre&#39;\u003C\u002Fcode>\u002F\u003Ccode>&#39;post&#39;\u003C\u002Fcode>) watcher","Queued through a component-level scheduler job, which is itself just one more effect in the same buffer",[306,307],"One effect throws (3.5, and 3.6 ≥ rc.10)","Every other queued effect still runs; the \u003Cstrong>first\u003C\u002Fstrong> error is rethrown once the whole batch is drained",[309,310],"One effect throws (any 3.6.0-alpha.1 – rc.9 pre-release)","The loop abandons mid-buffer; remaining effects are delivered later, attached to the next unrelated write",[312,313],"Two effects throw in one batch","Both still run; only the first effect&#39;s error is rethrown, the second&#39;s is discarded",[315,316],"\u003Ccode>app.config.errorHandler\u003C\u002Fcode>","Still fires for the thrown error in every version — this was never a reporting bug","table",{"id":319,"code":320,"type":134,"language":135,"highlight":321},"b58","\u002F\u002F Defensive pattern: don't depend on Vue isolating failures between effects\nconst safeValue = computed(() => {\n  try {\n    return riskyDerivation(source.value)\n  } catch (err) {\n    app.config.errorHandler?.(err, null, 'computed getter')\n    return fallbackValue\n  }\n})",[],{"id":323,"html":324,"type":83},"b59","\u003C!-- playground:start -->",{"id":326,"html":327,"text":327,"type":88,"level":31},"b60","🎮 Try it yourself",{"id":329,"html":330,"type":83},"b61","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-batch-watcher-throws\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":332,"html":333,"type":83},"b62","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":335,"html":336,"type":83},"b63","\u003C!-- playground:end -->",{"id":338,"html":339,"text":339,"type":88,"level":31},"b64","Key Takeaways",{"id":341,"type":94,"items":342,"ordered":18},"b65",[343,344,345,346,347],"A \u003Ccode>ref\u003C\u002Fcode> write doesn&#39;t call its watchers directly — it queues them into a shared notify buffer, and a single \u003Ccode>flush()\u003C\u002Fcode> loop walks that buffer afterward.","For the entire 3.6 pre-release line, from 3.6.0-alpha.1 through 3.6.0-rc.9, a throw in one effect could abandon that loop mid-walk, silently delaying unrelated effects until the next write. Fixed in 3.6.0-rc.10 (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674\">#15674\u003C\u002Fa>, \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676\">#15676\u003C\u002Fa>), matching how 3.5.x always behaved.","Even fixed, only the \u003Cem>first\u003C\u002Fem> error in a batch is rethrown to your code — later throwers still execute but their errors are swallowed.","Don&#39;t rely on Vue to isolate failures between independent watchers or computeds; guard the specific logic that can throw, inside the effect itself.","The \u003Ccode>nextTick\u003C\u002Fcode> DOM queue and this reactivity-level notify buffer are two different layers — your component&#39;s render job is just one more effect sitting in the same buffer as your watchers.",{"id":349,"html":350,"type":83},"b66","\u003C!-- quiz:start -->",{"id":352,"html":353,"text":353,"type":88,"level":31},"b67","🧠 Test yourself",{"id":355,"html":356,"type":83},"b68","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-batch-watcher-throws\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":358,"html":359,"type":83},"b69","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":361,"html":362,"type":83},"b70","\u003C!-- quiz:end -->",{"id":364,"html":365,"type":83},"b71","\u003Cp>A watcher that &quot;randomly&quot; stops firing isn&#39;t random — it&#39;s either your own logic or, if you were on a 3.6 pre-release before rc.10, a scheduling gap that&#39;s now closed. Either way, the fix is the same: stop assuming Vue hands you isolation you never asked for, and guard the part that can actually throw.\u003C\u002Fp>",{"id":367,"html":368,"type":83},"b72","\u003Cp>Have you hit a watcher that seemed to miss an update for no reason? Drop what you found in the comments — framework-internals bugs like this one are exactly the kind of thing worth comparing notes on.\u003C\u002Fp>",{"id":370,"html":371,"type":83},"b73","\u003C!-- related:start -->",{"id":373,"html":374,"text":374,"type":88,"level":31},"b74","📚 Read next",{"id":376,"type":94,"items":377,"ordered":18},"b75",[378,379,380],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive\">Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap\">Vue Composables: The Shared State Trap (+ Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates\">Vue nextTick Explained: How DOM Updates Are Batched\u003C\u002Fa>",{"id":382,"html":383,"type":83},"b76","\u003C!-- related:end -->",{"id":385,"type":386},"b77","divider",{"id":388,"html":389,"type":83},"b78","\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":391,"html":392,"type":83},"b79","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":394,"type":94,"items":395,"ordered":18},"b80",[396,397,398],"⭐ \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>","Two watchers, same `ref`, nothing to do with each other. One of them has a bug and throws. The other one — which has no bug at all — just stops updating. Not forever. It comes back to life the next time some *completely unrelated* ref changes somewhere else in your app. If you've ever stared at a watcher that \"randomly\" seemed to miss an update and blamed your own code, this is the kind of thing that can actually cause it — and across every Vue 3.6 pre-release — from the first alpha through the last rc before the fix — it genuinely did.\n\n## What You'll Learn\n\nBy the end of this article you'll be able to:\n\n- Explain what a reactive \"batch\" is and why Vue groups effect notifications together instead of firing them one at a time\n- Read Vue's actual `flush()` loop and predict what happens when one effect in it throws\n- Name the exact regression that shipped in Vue 3.6.0-rc.9, the GitHub issue that caught it, and the fix that landed in rc.10\n- Tell the difference between this reactivity-level batch and the separate DOM-update queue `nextTick` flushes\n- Write watcher and computed code that doesn't depend on Vue isolating your failures for you\n\n## Who This Is For\n\nYou've written `ref`, `watch`, and `computed` in Vue 3 with `\u003Cscript setup>` and you know the basics of dependency tracking. This article assumes that; if read-time tracking itself — how Vue even knows which ref a computed depends on — is still fuzzy, [Vue Reactivity: ref vs reactive](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-reactivity-explained-ref-vs-reactive-cheat-sheet-4nij) builds that foundation first. Useful background, not required reading.\n\n## Table of Contents\n\n- [The Problem: A Watcher That \"Randomly\" Stops Firing](#the-problem-a-watcher-that-randomly-stops-firing)\n- [The Mental Model: Writes Don't Notify — They Queue, Then Flush](#the-mental-model-writes-dont-notify-they-queue-then-flush)\n- [Building It Up: Inside flush()](#building-it-up-inside-flush)\n- [Edge Cases and Gotchas](#edge-cases-and-gotchas)\n- [Best Practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat Sheet](#cheat-sheet)\n- [Key Takeaways](#key-takeaways)\n\nThis is written against **Vue 3.5.x** (the current stable line on npm) and **Vue 3.6.0-rc.10** (verified September 2026), the release candidate for Vue's rewritten, [alien-signals](https:\u002F\u002Fgithub.com\u002Fstackblitz\u002Falien-signals)-based reactivity core. The regression this article walks through was never in any stable 3.5.x release — 3.5 doesn't even run this code, since the alien-signals-based rewrite only exists on the 3.6 line. But it's not a one-RC blip either: the unprotected `flush()` loop shipped as-is from the very first 3.6.0-alpha.1, through every beta and every release candidate up to and including rc.9, and was only fixed in rc.10. Everything about the underlying batch-and-flush mechanism, though, is how Vue's reactivity has worked since well before 3.6; it's the error-handling around it, specifically, that was missing for most of 3.6's pre-release life.\n\n## The Problem: A Watcher That \"Randomly\" Stops Firing\n\nSay you have two independent `watch` callbacks reading the same source — maybe one updates a UI label, the other logs to an analytics queue:\n\n```js\nimport { ref, computed, watch } from 'vue'\n\nconst user = ref({ name: 'Ava', age: 29 })\n\nconst label = computed(() => {\n  \u002F\u002F a bug: throws if `name` is ever missing\n  return user.value.name.toUpperCase()\n})\n\nwatch(label, (v) => console.log('[ui] label →', v), { flush: 'sync' })\nwatch(user, (v) => console.log('[analytics] user changed →', v.age), { flush: 'sync', deep: true })\n\nconst unrelated = ref(0)\n```\n\nBoth watchers are set to `flush: 'sync'` on purpose, so you can reason about the exact order things run in — no scheduler, no microtask, just \"the write happens, then the watchers that depend on it run.\" Now trigger the bug:\n\n```js\nuser.value = { age: 30 } \u002F\u002F no `name` — the computed's getter throws\n```\n\nOn Vue 3.5, and on Vue 3.6 from rc.10 onward, you get what you'd expect: the `[ui] label` watcher's getter throws inside `computed()`, the error is reported through Vue's error handling, and the `[analytics] user changed` watcher — which has nothing to do with `label` and nothing wrong with it — still runs, because `user` changed and it's watching `user`.\n\nOn every 3.6 pre-release up to and including rc.9, that second line never appeared. Not because the watcher was broken. Because of *when* it ran:\n\n```js\nconsole.clear()\nuser.value = { age: 30 }\n\u002F\u002F [nothing — both watchers were silent, including the one that should be fine]\n\nunrelated.value++\n\u002F\u002F [analytics] user changed → 30   ← shows up now, attached to the WRONG write\n```\n\nThe analytics watcher's callback didn't fire for the write that actually triggered it. It fired later, piggybacking on a completely unrelated `unrelated.value++`. If `unrelated` never changes again, that watcher simply never runs for that update at all. From the outside this looks exactly like \"the watcher missed an update\" — a bug you'd go looking for in your own code, because nothing in the stack trace points at Vue's internals.\n\nThis was filed as [vuejs\u002Fcore#15674](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674) and fixed by [PR #15676](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676), which shipped in **3.6.0-rc.10**. To understand *why* a throw in one watcher could ever delay a different watcher, you need the actual mechanism underneath every `ref` write — which is worth knowing regardless of this bug, because it explains ordering questions you'll hit constantly.\n\n## The Mental Model: Writes Don't Notify — They Queue, Then Flush\n\nHere's the picture most developers carry around, and it's wrong in a way that matters: \"when I write `ref.value = x`, Vue loops over everything that depends on it and runs each one, one after another, right there.\" That's not what happens.\n\n**The mental model:** a write doesn't call its dependents directly. It marks them **dirty** and pushes them into a single, shared **notify buffer** — one array, shared across every ref and computed in your whole app for the duration of that write. Once the write itself is done, Vue walks that buffer in a loop called `flush()`, calling `effect.notify()` on each entry in order. Only inside `notify()` does an individual watcher's callback, or a computed's recomputation, actually run.\n\nThat indirection is what makes a \"batch\" a real, inspectable thing instead of a metaphor. It's also why two unrelated watchers on two unrelated refs can end up sharing a buffer if their writes happen to be entangled by a computed in between (as `label` and `user` are above, via `user.value.name`) — and it's exactly the mechanism [`computed()`'s caching](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-computed-what-it-caches-and-when-it-reruns-1e03) rides on top of: a computed is dirtied through this same notify path, not recalculated eagerly on every write.\n\nThis is **not** the same queue [`nextTick` flushes](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp). `nextTick`'s job is scheduling *component re-renders* on a microtask so the DOM updates once per tick instead of once per write. The notify buffer described here sits one layer underneath that: a component's render job is itself just one more effect that gets pushed into this same buffer and walked by this same `flush()` loop. A `flush: 'sync'` watcher skips the component scheduler entirely and runs straight out of this loop, which is why the examples above use it — it makes the ordering deterministic enough to reason about on paper.\n\n## Building It Up: Inside flush()\n\nLet's build the mechanism up from nothing, the same way Vue's reactivity package does it internally. This is a simplified model of the real algorithm in `packages\u002Freactivity\u002Fsrc\u002Fsystem.ts` — not a toy metaphor, the actual control flow — small enough to read start to finish.\n\n**Stage 1 — one effect, one write.** No batching needed yet:\n\n```js\nfunction makeEffect(fn) {\n  return { notify: () => fn() }\n}\n\nconst e1 = makeEffect(() => console.log('effect 1 ran'))\ne1.notify() \u002F\u002F effect 1 ran\n```\n\n**Key concept:** `notify()` is the thing that actually executes a watcher's callback or re-runs a computed's getter. Everything from here on is about *when* and *in what order* `notify()` gets called across several effects.\n\n**Stage 2 — a shared buffer and a flush loop.** This is the real shape:\n\n```js\nlet notifyBuffer = []\nlet notifyIndex = 0\nlet notifyBufferLength = 0\n\nfunction queue(effect) {\n  notifyBuffer[notifyBufferLength++] = effect\n}\n\nfunction flush() {\n  while (notifyIndex \u003C notifyBufferLength) {\n    const effect = notifyBuffer[notifyIndex]\n    notifyBuffer[notifyIndex++] = undefined\n    effect.notify()\n  }\n  notifyIndex = 0\n  notifyBufferLength = 0\n}\n```\n\n**Key concept:** a write queues one or more effects into `notifyBuffer`, then calls `flush()` once. `flush()` walks the whole buffer in a plain loop — every queued effect gets its turn, in the order it was queued, synchronously, before `flush()` returns.\n\n**Stage 3 — introduce a throwing effect, with no protection.** This is what every Vue 3.6 pre-release actually shipped, from 3.6.0-alpha.1 through 3.6.0-rc.9:\n\n```js\nconst good1 = makeEffect(() => console.log('good watcher A ran'))\nconst bad = makeEffect(() => { throw new Error('getter blew up') })\nconst good2 = makeEffect(() => console.log('good watcher B ran'))\n\nqueue(good1)\nqueue(bad)\nqueue(good2)\n\nflush()\n\u002F\u002F good watcher A ran\n\u002F\u002F Uncaught Error: getter blew up\n\u002F\u002F (good watcher B never ran — and notifyIndex\u002FnotifyBufferLength\n\u002F\u002F  were left pointing mid-buffer, non-zero)\n```\n\nWalk through exactly what happened: `good1.notify()` runs and returns normally, `notifyIndex` advances to 1. `bad.notify()` throws. Because there's no `try`\u002F`catch` around the call, the exception propagates straight out of the `while` loop — skipping the two lines that reset `notifyIndex` and `notifyBufferLength` to 0. `good2` was already sitting in the buffer, but the loop never reaches it. The buffer isn't empty and the index isn't reset — it's just *abandoned*, mid-walk, with `good2` still inside it.\n\nThe next time **any** write anywhere calls `flush()` again, the `while (notifyIndex \u003C notifyBufferLength)` check is still true from the leftover state, so the loop picks up right where it left off and finally calls `good2.notify()` — attached to whatever unrelated write happened to trigger that next flush. That's the exact bug from the opening example.\n\n**Stage 4 — the fix, isolating each notify call.** This is what shipped in 3.6.0-rc.10, restoring 3.5's behavior:\n\n```js\nfunction flush() {\n  let hasError = false\n  let error\n  while (notifyIndex \u003C notifyBufferLength) {\n    const effect = notifyBuffer[notifyIndex]\n    notifyBuffer[notifyIndex++] = undefined\n    try {\n      effect.notify()\n    } catch (e) {\n      if (!hasError) {\n        hasError = true\n        error = e\n      }\n    }\n  }\n  notifyIndex = 0\n  notifyBufferLength = 0\n  if (hasError) throw error\n}\n```\n\n**Key concept:** a single `try`\u002F`catch` wraps each individual `effect.notify()` call, so a throw from one effect can't stop the loop from reaching the next one — the loop itself can no longer throw at all. That's what makes the reset lines right after the loop unconditional: there's no uncaught exception left to jump past them, so `notifyIndex` and `notifyBufferLength` always get reset to zero before the next write's flush. Only the *first* error that occurred is remembered, in `hasError`\u002F`error`, and rethrown once every queued effect has had its turn.\n\nRun the stage-3 example through this version and `good2 ran` prints immediately, in the same flush as the throw — and the error still surfaces to whoever triggered the write, just after everyone's had their notify.\n\n## Edge Cases and Gotchas\n\n- **Two throwing effects in the same batch.** Only the *first* error is captured and rethrown; the second (and any later) thrower still gets its `notify()` call — so its side effects before the `throw` statement still happen — but its exception is silently discarded. If you need to know about every failure in a batch, don't rely on the single rethrown error; catch inside each effect instead (see Best Practices).\n- **`flush: 'sync'` isn't required for this to matter.** The examples use it to make ordering legible on paper, but default (`'pre'`) and `'post'` watchers are notified through this exact same buffer and loop — they're just queued by a component-level scheduler job that itself sits in the buffer, rather than running immediately. A throw anywhere in that chain goes through the same per-effect `try`\u002F`catch` logic described above.\n- **This was never a stable-release bug, but it was a long-lived pre-release one.** It's worth repeating because version confusion is exactly how stale advice spreads in a framework ecosystem: 3.5.x never had it — it predates the alien-signals rewrite entirely — and it never reached a non-RC 3.6.0 release. But it wasn't confined to rc.9 either: the unprotected `flush()` shipped unchanged from 3.6.0-alpha.1 all the way through 3.6.0-rc.9, and was only fixed in rc.10. If you're pinned to an exact 3.6 pre-release for testing, pin to rc.10 or later.\n- **`app.config.errorHandler` still fires for the thrown error either way** — this bug was never about error *reporting* going missing, only about a sibling effect's *execution* being delayed. Don't confuse \"I got an error callback\" with \"every watcher that should have run, ran.\"\n- **A computed's own dirty flag isn't affected by this.** The computed in the opening example (`label`) correctly stays dirty and will recompute (and rethrow) on its next read regardless of this bug; what the bug delayed was the *other* watcher, not the throwing one.\n\n## Best Practices\n\n- **Don't let a risky computed or watcher getter run unguarded** if a sibling's correctness depends on timing. Wrap the risky part in its own `try`\u002F`catch` and fall back to a safe value, rather than letting the exception travel up into Vue's flush loop at all:\n\n  ```js\n  const label = computed(() => {\n    try {\n      return user.value.name.toUpperCase()\n    } catch {\n      return '—'\n    }\n  })\n  ```\n\n- **Treat watchers on the same source as independent, not ordered.** Even with the fix, only the first error in a batch is rethrown — you cannot depend on \"if watcher A didn't throw, I'll find out about watcher B's error the same way.\" Each effect's failure handling is its own responsibility.\n- **Use `app.config.errorHandler` for centralized reporting**, not as a substitute for per-watcher guards — it tells you *that* something threw, not which of several effects in the same batch also ran cleanly.\n- **If you're testing against a 3.6 release candidate today, pin to rc.10 or newer.** `npm view vue dist-tags` tells you the current `rc` tag; don't trust a cached `node_modules` from a few weeks ago to reflect it.\n\n## FAQ\n\n### Does this bug affect Vue 3.5?\n\nNo. It came in with the alien-signals-based reactivity rewrite that's exclusive to the 3.6 line — 3.5.x doesn't run this code at all, and every 3.5.x release behaves like the fixed version described in Stage 4. Within 3.6, the bug wasn't limited to `rc.9` either: the unprotected `flush()` shipped unchanged from `3.6.0-alpha.1` through `3.6.0-rc.9`, and was fixed in `3.6.0-rc.10`.\n\n### Is the batch described here the same as the `nextTick` queue?\n\nNo — they're adjacent, not the same thing. `nextTick` schedules component re-renders on a microtask so the DOM updates once per tick. The notify buffer in this article is the layer underneath: a component's render job is itself one effect queued into this buffer, alongside your `watch` and `computed` effects. `flush: 'sync'` watchers skip the `nextTick` scheduling and run directly out of this loop.\n\n### Will my component fail to re-render if a sibling watcher throws?\n\nWith the fix (3.6.0-rc.10 and later, and all of 3.5), no — your render job still gets its `notify()` call in the same flush, exactly like `good2` in Stage 4. On any unpatched pre-release (3.6.0-alpha.1 through rc.9), it theoretically could, for the same reason any other queued effect could: a throw earlier in the buffer would abandon the loop before reaching it.\n\n### Should I stop using `watch()` and handle everything with `try`\u002F`catch` instead?\n\nNo — `watch()` and `computed()` are still the right tool. The takeaway isn't \"avoid the reactivity system,\" it's \"don't assume Vue isolates failures between independent effects for you.\" Guard the specific logic that can realistically throw, inside the watcher or computed itself.\n\n### Where can I read the actual fix?\n\n[vuejs\u002Fcore#15674](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674) has the original repro and root-cause analysis; [PR #15676](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676) has the diff that shipped in 3.6.0-rc.10.\n\n## Cheat Sheet\n\n| What | How it behaves |\n| --- | --- |\n| A `ref` write | Marks dependent effects dirty, queues them into a shared notify buffer — does **not** call them directly |\n| `flush()` | Walks the notify buffer in a loop, calling `effect.notify()` on each queued effect in order |\n| `flush: 'sync'` watcher | Runs straight out of this loop, with no `nextTick` scheduling in between |\n| Default (`'pre'`\u002F`'post'`) watcher | Queued through a component-level scheduler job, which is itself just one more effect in the same buffer |\n| One effect throws (3.5, and 3.6 ≥ rc.10) | Every other queued effect still runs; the **first** error is rethrown once the whole batch is drained |\n| One effect throws (any 3.6.0-alpha.1 – rc.9 pre-release) | The loop abandons mid-buffer; remaining effects are delivered later, attached to the next unrelated write |\n| Two effects throw in one batch | Both still run; only the first effect's error is rethrown, the second's is discarded |\n| `app.config.errorHandler` | Still fires for the thrown error in every version — this was never a reporting bug |\n\n```js\n\u002F\u002F Defensive pattern: don't depend on Vue isolating failures between effects\nconst safeValue = computed(() => {\n  try {\n    return riskyDerivation(source.value)\n  } catch (err) {\n    app.config.errorHandler?.(err, null, 'computed getter')\n    return fallbackValue\n  }\n})\n```\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-batch-watcher-throws\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Key Takeaways\n\n- A `ref` write doesn't call its watchers directly — it queues them into a shared notify buffer, and a single `flush()` loop walks that buffer afterward.\n- For the entire 3.6 pre-release line, from 3.6.0-alpha.1 through 3.6.0-rc.9, a throw in one effect could abandon that loop mid-walk, silently delaying unrelated effects until the next write. Fixed in 3.6.0-rc.10 ([#15674](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fissues\u002F15674), [#15676](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15676)), matching how 3.5.x always behaved.\n- Even fixed, only the *first* error in a batch is rethrown to your code — later throwers still execute but their errors are swallowed.\n- Don't rely on Vue to isolate failures between independent watchers or computeds; guard the specific logic that can throw, inside the effect itself.\n- The `nextTick` DOM queue and this reactivity-level notify buffer are two different layers — your component's render job is just one more effect sitting in the same buffer as your watchers.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-batch-watcher-throws\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\nA watcher that \"randomly\" stops firing isn't random — it's either your own logic or, if you were on a 3.6 pre-release before rc.10, a scheduling gap that's now closed. Either way, the fix is the same: stop assuming Vue hands you isolation you never asked for, and guard the part that can actually throw.\n\nHave you hit a watcher that seemed to miss an update for no reason? Drop what you found in the comments — framework-internals bugs like this one are exactly the kind of thing worth comparing notes on.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-ref-vs-reactive)\n- [Vue Composables: The Shared State Trap (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap)\n- [Vue nextTick Explained: How DOM Updates Are Batched](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-nexttick-batching-dom-updates)\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":47,"canonical":401,"description":49},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-reactivity-batch-watcher-throws","01a0f781-ba1b-7528-8bc8-bd656208fe6f",{"name":404,"part":405,"total":405,"items":406},"Vue Deep Dive",7,[407,412,417,421,426,430,434],{"slug":408,"title":409,"publishedAt":410,"readingMinutes":411},"vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)","2026-08-24T13:57:38.080Z",14,{"slug":413,"title":414,"publishedAt":415,"readingMinutes":416},"vue-weekly-nexttick-batching-dom-updates","Vue nextTick Explained: How DOM Updates Are Batched","2026-09-14T12:19:00.606Z",13,{"slug":418,"title":419,"publishedAt":420,"readingMinutes":416},"vue-weekly-composables-shared-state-trap","Vue Composables: The Shared State Trap (+ Cheat Sheet)","2026-09-22T19:37:11.460Z",{"slug":422,"title":423,"publishedAt":424,"readingMinutes":425},"vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns","2026-09-30T12:10:53.362Z",12,{"slug":427,"title":428,"publishedAt":429,"readingMinutes":416},"vue-weekly-definemodel-v-model-contract","Vue defineModel: The v-model Contract, Explained","2026-10-05T06:11:02.623Z",{"slug":431,"title":432,"publishedAt":433,"readingMinutes":51},"vue-weekly-watch-vs-watcheffect","Vue watch vs watchEffect: The Complete Guide","2026-10-12T06:10:54.504Z",{"slug":46,"title":47,"publishedAt":52,"readingMinutes":51},{"id":436,"locked":18},"01a0f781-ba3f-73ac-9d9f-84d4304351e3",[438],{"id":439,"slug":46,"title":440,"_count":441},"01a0f781-ba5b-7096-970c-a2a61c318b1a","Vue 3.6: The Notify Buffer & the Watcher-Throws Bug",{"questions":442},8,[444],{"locale":13,"slug":46},{"id":439,"slug":46,"title":440,"_count":446,"questionCount":442},{"questions":442},[448,450,452,456,458,462,466,470,474,477,480,484],{"slug":72,"name":73,"articles":449},146,{"slug":66,"name":67,"articles":451},119,{"slug":453,"name":454,"articles":455},"frontend","Frontend",87,{"slug":69,"name":70,"articles":457},56,{"slug":459,"name":460,"articles":461},"css","Css",44,{"slug":463,"name":464,"articles":465},"performance","Performance",24,{"slug":467,"name":468,"articles":469},"typescript","Typescript",19,{"slug":471,"name":472,"articles":473},"react","React",18,{"slug":475,"name":476,"articles":411},"browser","Browser",{"slug":478,"name":479,"articles":425},"node","Node",{"slug":481,"name":482,"articles":483},"programming","Programming",11,{"slug":485,"name":486,"articles":487},"accessibility","Accessibility",10,[],{"items":490,"meta":593},[491,503,518,533,548,563,578],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":492,"likeCount":19,"commentCount":19,"author":493,"vertical":494,"topic":495,"tags":496,"_count":501,"playground":502,"hasQuiz":17,"hasPlayground":17},34,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[497,498,499,500],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":46},{"id":504,"slug":431,"title":432,"subtitle":48,"excerpt":505,"coverUrl":506,"locale":13,"readingMinutes":51,"publishedAt":433,"viewCount":507,"likeCount":19,"commentCount":19,"author":508,"vertical":509,"topic":510,"tags":511,"_count":516,"playground":517,"hasQuiz":17,"hasPlayground":17},"01a0ec73-2e41-711f-9f8a-b6c9bae1e054","Vue's watch() and watchEffect() look interchangeable but track dependencies, run timing, and cleanup differently. The exact rules, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-watch-vs-watcheffect.png",309,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[512,513,514,515],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":431},{"id":519,"slug":427,"title":428,"subtitle":48,"excerpt":520,"coverUrl":521,"locale":13,"readingMinutes":416,"publishedAt":429,"viewCount":522,"likeCount":19,"commentCount":19,"author":523,"vertical":524,"topic":525,"tags":526,"_count":531,"playground":532,"hasQuiz":17,"hasPlayground":17},"01a0af18-2d15-7347-b2f4-6cdfa2ce56fa","Vue's defineModel macro replaces the modelValue prop and update:modelValue event with one ref. The exact v-model contract behind it, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-definemodel-v-model-contract.png",503,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[527,528,529,530],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":427},{"id":534,"slug":422,"title":423,"subtitle":48,"excerpt":535,"coverUrl":536,"locale":13,"readingMinutes":425,"publishedAt":424,"viewCount":537,"likeCount":19,"commentCount":19,"author":538,"vertical":539,"topic":540,"tags":541,"_count":546,"playground":547,"hasQuiz":17,"hasPlayground":17},"01a08af7-9d6f-74df-b76e-f6c445f54a47","Vue's computed() caches its result and only reruns when a tracked dependency changes, not on every read. The exact caching and invalidation model, verified against Vue 3.5.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-computed-caching-invalidation.png",461,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[542,543,544,545],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":422},{"id":549,"slug":418,"title":419,"subtitle":48,"excerpt":550,"coverUrl":551,"locale":13,"readingMinutes":416,"publishedAt":420,"viewCount":552,"likeCount":19,"commentCount":19,"author":553,"vertical":554,"topic":555,"tags":556,"_count":561,"playground":562,"hasQuiz":17,"hasPlayground":17},"01a066ea-3098-75c9-b7e5-1d5426f773eb","Why two Vue components using the same composable can end up sharing state by accident, the mental model that explains it, and how to fix or embrace it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-composables-shared-state-trap.png",597,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[557,558,559,560],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":418},{"id":564,"slug":413,"title":414,"subtitle":48,"excerpt":565,"coverUrl":566,"locale":13,"readingMinutes":416,"publishedAt":415,"viewCount":567,"likeCount":19,"commentCount":19,"author":568,"vertical":569,"topic":570,"tags":571,"_count":576,"playground":577,"hasQuiz":17,"hasPlayground":17},"01a04446-5a00-7624-b775-c73c6c035e67","How Vue batches reactive writes into one DOM update, why the DOM looks stale right after a change, and how nextTick and watch's flush option fix it.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-nexttick-batching-dom-updates.png",607,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[572,573,574,575],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":413},{"id":579,"slug":408,"title":409,"subtitle":48,"excerpt":580,"coverUrl":581,"locale":13,"readingMinutes":411,"publishedAt":410,"viewCount":582,"likeCount":19,"commentCount":75,"author":583,"vertical":584,"topic":585,"tags":586,"_count":591,"playground":592,"hasQuiz":17,"hasPlayground":17},"01a03410-2db8-735b-88b2-5285459dc915","A complete guide to Vue reactivity: how ref and reactive really work, why destructuring breaks updates, when to use each, and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-reactivity-ref-vs-reactive.png",921,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[587,588,589,590],{"slug":61,"name":62,"color":48},{"slug":66,"name":67,"color":48},{"slug":69,"name":70,"color":48},{"slug":72,"name":73,"color":48},{"assessments":75},{"slug":408},{"page":75,"perPage":405,"total":405,"totalPages":75},"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> ref\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> computed\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> watch \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'vue'\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> user \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> name\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Ava'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> age\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">29\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> label \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\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> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-cmnt\">\u002F\u002F a bug: throws if `name` is ever missing\n\u003C\u002Fspan>  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> user\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>name\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">toUpperCase\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>label\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>v\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\">'[ui] label →'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> v\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'sync'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-func\">watch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>user\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>v\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\">'[analytics] user changed →'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> v\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>age\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> flush\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'sync'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> deep\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> unrelated \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">ref\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-oneline\" data-lang=\"js\">user\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> age\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">30\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F no `name` — the computed's getter throws\u003C\u002Fspan>\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">console\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>\nuser\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> age\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">30\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F [nothing — both watchers were silent, including the one that should be fine]\n\u003C\u002Fspan>\nunrelated\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F [analytics] user changed → 30   ← shows up now, attached to the WRONG write\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">makeEffect\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>fn\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> notify\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fn\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> e1 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">makeEffect\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\">'effect 1 ran'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\ne1\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">notify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F\u002F effect 1 ran\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>\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>\u003Cdiv>17\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> notifyBuffer \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> notifyIndex \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> notifyBufferLength \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">queue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>effect\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  notifyBuffer\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>notifyBufferLength\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> effect\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  \u003Cspan class=\"shj-kwd\">while\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>notifyIndex \u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan> notifyBufferLength\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> effect \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> notifyBuffer\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>notifyIndex\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\n    notifyBuffer\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>notifyIndex\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">undefined\u003C\u002Fspan>\n    effect\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">notify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  notifyIndex \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n  notifyBufferLength \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\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>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> good1 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">makeEffect\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\">'good watcher A ran'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> bad \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">makeEffect\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> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Error\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'getter blew up'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> good2 \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">makeEffect\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\">'good watcher B ran'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">queue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>good1\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-func\">queue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>bad\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-func\">queue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>good2\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">flush\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F good watcher A ran\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F Uncaught Error: getter blew up\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F (good watcher B never ran — and notifyIndex\u002FnotifyBufferLength\n\u003C\u002Fspan>\u003Cspan class=\"shj-cmnt\">\u002F\u002F  were left pointing mid-buffer, non-zero)\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>\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>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003Cdiv>19\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\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  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> hasError \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">false\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> error\n  \u003Cspan class=\"shj-kwd\">while\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>notifyIndex \u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan> notifyBufferLength\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> effect \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> notifyBuffer\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>notifyIndex\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\n    notifyBuffer\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>notifyIndex\u003Cspan class=\"shj-oper\">++\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">undefined\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n      effect\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">notify\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>e\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \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>hasError\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n        hasError \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bool\">true\u003C\u002Fspan>\n        error \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> e\n      \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n    \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  notifyIndex \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n  notifyBufferLength \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>hasError\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> error\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>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F Defensive pattern: don't depend on Vue isolating failures between effects\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> safeValue \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">computed\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> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">riskyDerivation\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>source\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>value\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>err\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>config\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">errorHandler\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">?.\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>err\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-num\">null\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'computed getter'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> fallbackValue\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":603},[]]