From 022d7908aceb6ab146f72beeeeef77ff02a73130 Mon Sep 17 00:00:00 2001 From: Saxon Landers Date: Mon, 1 Jun 2026 16:46:39 +1000 Subject: [PATCH] fix(reactivity): preserve watch callback return value when wrapped for `once: true` --- packages/reactivity/__tests__/watch.spec.ts | 41 +++++++++++++++++++++ packages/reactivity/src/watch.ts | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/reactivity/__tests__/watch.spec.ts b/packages/reactivity/__tests__/watch.spec.ts index 9bec54e5f68..db19070e03d 100644 --- a/packages/reactivity/__tests__/watch.spec.ts +++ b/packages/reactivity/__tests__/watch.spec.ts @@ -116,6 +116,47 @@ describe('watch', () => { ]) }) + test('call option with async error handling', async () => { + const onError = vi.fn() + const call: WatchOptions['call'] = function call(fn, type, args) { + if (Array.isArray(fn)) { + fn.forEach(f => call(f, type, args)) + return + } + fn.apply(null, args).catch((error: unknown) => { + onError(error) + }) + } + + const source1 = ref(0) + watch( + source1, + async () => { + throw 'oops in watch' + }, + { call }, + ) + + source1.value++ + await nextTick() + expect(onError.mock.calls.length).toBe(1) + expect(onError.mock.calls[0]).toMatchObject(['oops in watch']) + + const source2 = ref(0) + watch( + source2, + async () => { + throw 'oops in once watch' + }, + { call, once: true }, + ) + + source2.value++ + await nextTick() + expect(onError.mock.calls.length).toBe(2) + expect(onError.mock.calls[1]).toMatchObject(['oops in once watch']) + }) + test('watch with onWatcherCleanup', async () => { let dummy = 0 let source: Ref diff --git a/packages/reactivity/src/watch.ts b/packages/reactivity/src/watch.ts index 6bc009985e0..9d62323f0da 100644 --- a/packages/reactivity/src/watch.ts +++ b/packages/reactivity/src/watch.ts @@ -221,8 +221,9 @@ export function watch( if (once && cb) { const _cb = cb cb = (...args) => { - _cb(...args) + const res = _cb(...args) watchHandle() + return res } }