-
-
Notifications
You must be signed in to change notification settings - Fork 8.9k
feat(watch): add support for AbortController
#13861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: minor
Are you sure you want to change the base?
Conversation
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughAdds 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
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)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/reactivity/src/watch.ts (1)
126-126
: Handle pre-aborted signals and remove abort listener on manual stop
- If
signal.aborted
is already true, stop immediately.- When
stop()
is called manually, remove the abort listener to avoid retaining references.Apply:
const watchHandle: WatchHandle = () => { - effect.stop() + // detach abort listener if present + if (signal && 'removeEventListener' in signal) { + signal.removeEventListener('abort', watchHandle) + } + effect.stop() if (scope && scope.active) { remove(scope.effects, effect) } } if (signal) { - signal.addEventListener('abort', watchHandle, { once: true }) + if (signal.aborted) { + watchHandle() + } else { + signal.addEventListener('abort', watchHandle, { once: true }) + } }Also applies to: 214-221, 230-233
packages/reactivity/__tests__/watch.spec.ts (1)
293-312
: Add a test for pre-aborted signalsVerify that a watcher with an already-aborted signal never runs and detaches immediately. This guards the new fast-path.
Example:
it('stop multiple watches by abort controller', async () => { ... }) + +it('pre-aborted signal stops watch immediately', async () => { + const controller = new AbortController() + controller.abort() + const state = ref(0) + const cb = vi.fn() + watch(state, cb, { signal: controller.signal, immediate: true }) + await nextTick() + expect(cb).not.toHaveBeenCalled() + state.value++ + await nextTick() + expect(cb).not.toHaveBeenCalled() +})packages/runtime-core/__tests__/apiWatch.spec.ts (1)
435-475
: Good coverage for abort-driven stopBoth effect and source variants are validated. Consider mirroring the pre-aborted case here as well for completeness, but not required.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/reactivity/__tests__/watch.spec.ts
(1 hunks)packages/reactivity/src/watch.ts
(3 hunks)packages/runtime-core/__tests__/apiWatch.spec.ts
(1 hunks)packages/runtime-core/src/apiWatch.ts
(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
packages/reactivity/__tests__/watch.spec.ts (3)
packages/reactivity/src/watch.ts (1)
watch
(121-334)packages/runtime-core/src/apiWatch.ts (1)
watch
(130-143)packages/runtime-core/src/scheduler.ts (1)
nextTick
(61-67)
packages/runtime-core/src/apiWatch.ts (2)
packages/reactivity/src/effect.ts (1)
DebuggerOptions
(23-26)packages/runtime-core/src/index.ts (2)
DebuggerOptions
(222-222)WatchEffectOptions
(232-232)
packages/runtime-core/__tests__/apiWatch.spec.ts (2)
packages/runtime-core/src/apiWatch.ts (2)
watchEffect
(59-64)watch
(130-143)packages/reactivity/src/watch.ts (1)
watch
(121-334)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Redirect rules
- GitHub Check: Header rules
- GitHub Check: Pages changed
🔇 Additional comments (1)
packages/runtime-core/src/apiWatch.ts (1)
68-69
: LGTM — signatures widened correctly
watchPostEffect
/watchSyncEffect
now accept the same base options (includingsignal
) without exposingflush
in their types. This matches usage and keeps the API tight.Also applies to: 79-80
Size ReportBundles
Usages
|
@vue/compiler-core
@vue/compiler-dom
@vue/compiler-sfc
@vue/compiler-ssr
@vue/compiler-vapor
@vue/reactivity
@vue/runtime-core
@vue/runtime-dom
@vue/runtime-vapor
@vue/server-renderer
@vue/shared
vue
@vue/compat
commit: |
Can you do the same for effectScope? Or perhaps the implementation of effectScope is enough and watch doesn't need this. |
We should probably benchmark this. It would be good to have this for const {signal} = getCurrentScope()
fetch(url, {signal}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally would love to see it landed!
AbortController
to watch
AbortController
That looks awesome! I’ve opened a new PR for Thank you all for the reviews and suggestions! 💚 |
0d5dc3e
to
c7f5574
Compare
❌ Deploy Preview for vue-sfc-playground failed. Why did it fail? →
|
c7f5574
to
04ce238
Compare
LGTM. |
This PR adds support for a new option
signal
to thewatch
API in@vue/reactivity
.It accepts an
AbortSignal
. When provided, the watcher will be stopped once the correspondingAbortController
is aborted.This provides a more flexible way to stop multiple watchers at once by sharing a single
AbortController
.Before:
After:
Summary by CodeRabbit
New Features
Tests