Skip to content

Commit 0d5dc3e

Browse files
committed
feat(reactivity): add support for AbortController to watch
1 parent 75220c7 commit 0d5dc3e

File tree

4 files changed

+76
-4
lines changed

4 files changed

+76
-4
lines changed

packages/reactivity/__tests__/watch.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,25 @@ describe('watch', () => {
289289
value.value = true
290290
expect(value.value).toBe(false)
291291
})
292+
293+
it('stop multiple watches by abort controller', async () => {
294+
const controller = new AbortController()
295+
const state = ref(0)
296+
const cb1 = vi.fn()
297+
const cb2 = vi.fn()
298+
watch(state, cb1, { signal: controller.signal })
299+
watch(state, cb2, { signal: controller.signal })
300+
301+
state.value++
302+
await nextTick()
303+
expect(cb1).toHaveBeenCalledTimes(1)
304+
expect(cb2).toHaveBeenCalledTimes(1)
305+
306+
controller.abort()
307+
state.value++
308+
await nextTick()
309+
// should not run callback
310+
expect(cb1).toHaveBeenCalledTimes(1)
311+
expect(cb2).toHaveBeenCalledTimes(1)
312+
})
292313
})

packages/reactivity/src/watch.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type WatchCallback<V = any, OV = any> = (
4747
export type OnCleanup = (cleanupFn: () => void) => void
4848

4949
export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
50+
signal?: AbortSignal
5051
immediate?: Immediate
5152
deep?: boolean | number
5253
once?: boolean
@@ -122,7 +123,7 @@ export function watch(
122123
cb?: WatchCallback | null,
123124
options: WatchOptions = EMPTY_OBJ,
124125
): WatchHandle {
125-
const { immediate, deep, once, scheduler, augmentJob, call } = options
126+
const { immediate, deep, once, signal, scheduler, augmentJob, call } = options
126127

127128
const warnInvalidSource = (s: unknown) => {
128129
;(options.onWarn || warn)(
@@ -226,6 +227,10 @@ export function watch(
226227
}
227228
}
228229

230+
if (signal) {
231+
signal.addEventListener('abort', watchHandle, { once: true })
232+
}
233+
229234
let oldValue: any = isMultiSource
230235
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
231236
: INITIAL_WATCHER_VALUE

packages/runtime-core/__tests__/apiWatch.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,48 @@ describe('api: watch', () => {
432432
expect(dummy).toBe(1)
433433
})
434434

435+
it('stopping the watcher (effect) by abort controller', async () => {
436+
const controller = new AbortController()
437+
const state = reactive({ count: 0 })
438+
let dummy
439+
watchEffect(
440+
() => {
441+
dummy = state.count
442+
},
443+
{ signal: controller.signal },
444+
)
445+
expect(dummy).toBe(0)
446+
447+
controller.abort()
448+
state.count++
449+
await nextTick()
450+
// should not update
451+
expect(dummy).toBe(0)
452+
})
453+
454+
it('stopping the watcher (with source) by abort controller', async () => {
455+
const controller = new AbortController()
456+
const state = reactive({ count: 0 })
457+
let dummy
458+
watch(
459+
() => state.count,
460+
count => {
461+
dummy = count
462+
},
463+
{ signal: controller.signal },
464+
)
465+
466+
state.count++
467+
await nextTick()
468+
expect(dummy).toBe(1)
469+
470+
controller.abort()
471+
state.count++
472+
await nextTick()
473+
// should not update
474+
expect(dummy).toBe(1)
475+
})
476+
435477
it('cleanup registration (effect)', async () => {
436478
const state = reactive({ count: 0 })
437479
const cleanup = vi.fn()

packages/runtime-core/src/apiWatch.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ type MapSources<T, Immediate> = {
4141
: never
4242
}
4343

44-
export interface WatchEffectOptions extends DebuggerOptions {
44+
export interface BaseWatchEffectOptions extends DebuggerOptions {
45+
signal?: AbortSignal
46+
}
47+
48+
export interface WatchEffectOptions extends BaseWatchEffectOptions {
4549
flush?: 'pre' | 'post' | 'sync'
4650
}
4751

@@ -61,7 +65,7 @@ export function watchEffect(
6165

6266
export function watchPostEffect(
6367
effect: WatchEffect,
64-
options?: DebuggerOptions,
68+
options?: BaseWatchEffectOptions,
6569
): WatchHandle {
6670
return doWatch(
6771
effect,
@@ -72,7 +76,7 @@ export function watchPostEffect(
7276

7377
export function watchSyncEffect(
7478
effect: WatchEffect,
75-
options?: DebuggerOptions,
79+
options?: BaseWatchEffectOptions,
7680
): WatchHandle {
7781
return doWatch(
7882
effect,

0 commit comments

Comments
 (0)