core icon indicating copy to clipboard operation
core copied to clipboard

feat(watch): add support for `AbortController`

Open 9romise opened this issue 3 months ago • 8 comments

This PR adds support for a new option signal to the watch API in @vue/reactivity.

It accepts an AbortSignal. When provided, the watcher will be stopped once the corresponding AbortController is aborted.

This provides a more flexible way to stop multiple watchers at once by sharing a single AbortController.

Before:

const count = ref(0)

const cb1 = () => {}
const cb2 = () => {}

const stop1 = watch(count, cb1)
const stop2 = watch(count, cb2)

stop1()
stop2()

After:

const count = ref(0)
const controller = new AbortController()

const cb1 = () => {}
const cb2 = () => {}

watch(count, cb1, { signal: controller.signal })
watch(count, cb2, { signal: controller.signal })

controller.abort()

Summary by CodeRabbit

  • New Features

    • Added AbortController-based cancellation for reactive watchers. You can pass a signal to cancel one or multiple watchers at once. Works across effect and source-based watchers, including post and sync variants. Existing behavior remains unchanged unless a signal is provided.
  • Tests

    • Added tests verifying that aborting a shared signal stops all associated watchers and that no further updates occur after cancellation.

9romise avatar Sep 09 '25 03:09 9romise

Walkthrough

Adds AbortSignal-based cancellation to reactivity watchers. The watch API now accepts an optional signal to stop a watcher on abort. Runtime-core exposes a BaseWatchEffectOptions with signal and updates watchPostEffect/watchSyncEffect signatures. Tests verify abort-driven cancellation for watch, watchEffect, and multiple watches sharing one signal.

Changes

Cohort / File(s) Summary
Reactivity: AbortSignal support in watch
packages/reactivity/src/watch.ts
Adds signal?: AbortSignal to WatchOptions; registers abort listener to stop the watch handle when signaled. Existing behavior unchanged when no signal provided.
Reactivity tests: watch abort behavior
packages/reactivity/__tests__/watch.spec.ts
Adds test ensuring two watches sharing one AbortSignal are both stopped after abort.
Runtime-core: options surface for watch effects
packages/runtime-core/src/apiWatch.ts
Introduces BaseWatchEffectOptions with optional signal; WatchEffectOptions extends it; updates watchPostEffect and watchSyncEffect signatures to accept BaseWatchEffectOptions.
Runtime-core tests: aborting watchers/effects
packages/runtime-core/__tests__/apiWatch.spec.ts
Adds tests verifying AbortController stops watchEffect and watch watchers from reacting after abort.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant AC as AbortController
  participant W as watch()
  participant E as Reactive Effect
  participant H as WatchHandle

  C->>AC: const { signal } = new AbortController()
  C->>W: watch(source, cb, { signal })
  W->>E: create reactive effect
  W-->>H: return handle (stop)

  Note over E,H: Normal operation
  E-->>C: on source change -> invoke cb

  Note over AC,H: Abort path (new)
  C->>AC: controller.abort()
  AC-->>W: signal "abort" event
  W->>H: H.stop()
  H-->>E: teardown effect (unsubscribe)

  Note over E: After abort: no further cb invocations

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change, namely adding support for AbortController to the watch API in the reactivity package, and follows the conventional commit format with scope and type.
Description Check ✅ Passed The description clearly outlines the new signal option for the watch API, explains its behavior with AbortController, and provides before-and-after code examples that directly relate to the changeset.

Poem

I twitch my ears at signals’ call,
A whisper: “Abort!”—and I stop, that’s all.
Two watches nap beneath one tree,
One puff of wind, both wander free.
I thump the ground—tests pass, of course! 🐇
Now onward hop, with lighter force.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the core feature addition—support for AbortController in the watch API—using clear, concise phrasing that directly reflects the main change introduced by the pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • [ ] 📝 Generate Docstrings
🧪 Generate unit tests
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Sep 09 '25 03:09 coderabbitai[bot]

Size Report

Bundles

File Size Gzip Brotli
compiler-dom.global.prod.js 84 kB 29.8 kB 26.3 kB
runtime-dom.global.prod.js 104 kB (+71 B) 39.2 kB (+41 B) 35.3 kB (+19 B)
vue.global.prod.js 162 kB (+71 B) 59.3 kB (+40 B) 52.9 kB (+14 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47.2 kB 18.4 kB 16.9 kB
createApp 56.1 kB (+71 B) 21.6 kB (+30 B) 19.7 kB (+26 B)
createApp + vaporInteropPlugin 68.7 kB (+71 B) 25.9 kB (+31 B) 23.7 kB (+62 B)
createVaporApp 20.9 kB 8.26 kB 7.57 kB
createSSRApp 60.4 kB (+71 B) 23.4 kB (+27 B) 21.3 kB (+32 B)
defineCustomElement 61.1 kB (+71 B) 23.2 kB (+29 B) 21.1 kB (+33 B)
overall 70.5 kB (+71 B) 26.9 kB (+43 B) 24.5 kB (+51 B)

github-actions[bot] avatar Sep 09 '25 03:09 github-actions[bot]

Open in StackBlitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@13861
@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@13861
@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@13861
@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@13861
@vue/compiler-vapor

npm i https://pkg.pr.new/@vue/compiler-vapor@13861
@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@13861
@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@13861
@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@13861
@vue/runtime-vapor

npm i https://pkg.pr.new/@vue/runtime-vapor@13861
@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@13861
@vue/shared

npm i https://pkg.pr.new/@vue/shared@13861
vue

npm i https://pkg.pr.new/vue@13861
@vue/compat

npm i https://pkg.pr.new/@vue/compat@13861

commit: 2ceaf78

pkg-pr-new[bot] avatar Sep 09 '25 03:09 pkg-pr-new[bot]

Can you do the same for effectScope? Or perhaps the implementation of effectScope is enough and watch doesn't need this.

ferferga avatar Sep 16 '25 16:09 ferferga

We should probably benchmark this.

It would be good to have this for effectScope and watch. This would allow us to get the signal with getCurrentScope.

const {signal} = getCurrentScope()

fetch(url, {signal})

OrbisK avatar Sep 17 '25 06:09 OrbisK

We should probably benchmark this.

It would be good to have this for effectScope and watch. This would allow us to get the signal with getCurrentScope.

const {signal} = getCurrentScope()

fetch(url, {signal})

That looks awesome! I’ve opened a new PR for effectScope — I’m not entirely sure if this aligns with what you had in mind, so feel free to take a look and leave your feedback there!

Thank you all for the reviews and suggestions! 💚

9romise avatar Sep 19 '25 15:09 9romise

Deploy Preview for vue-sfc-playground failed. Why did it fail? →

Name Link
Latest commit 04ce2385acf1ae46aeebb64fd15092809a2d74ce
Latest deploy log https://app.netlify.com/projects/vue-sfc-playground/deploys/68d2375cdbe1320008f84f7d

netlify[bot] avatar Sep 23 '25 05:09 netlify[bot]

LGTM. Note: Documentation at https://vuejs.org/api/reactivity-core.html#watch needs to be updated when this feature is landed.

edison1105 avatar Sep 23 '25 07:09 edison1105