Repository: vuejs/composition-api
Pull request #751 (2021-07-07 09:58:50)

fix(watch): errors thrown in the asynchronous callback function in watch will not be caught.
fix: #749 


Patch:
diff --git a/src/apis/watch.ts b/src/apis/watch.ts
index c3cb5a8..abddb89 100644
--- a/src/apis/watch.ts
+++ b/src/apis/watch.ts
@@ -235,14 +235,14 @@ function createWatcher(
     ) {
       return fn
     }
-    return (((...args: any[]) =>
+    return ((...args: any[]) =>
       queueFlushJob(
         vm,
         () => {
           fn(...args)
         },
         flushMode as 'pre' | 'post'
-      )) as any) as T
+      )) as any as T
   }
 
   // effect watch
@@ -320,7 +320,7 @@ function createWatcher(
   const applyCb = (n: any, o: any) => {
     // cleanup before running cb again
     runCleanup()
-    cb(n, o, registerCleanup)
+    return cb(n, o, registerCleanup)
   }
   let callback = createScheduler(applyCb)
   if (options.immediate) {
@@ -330,10 +330,10 @@ function createWatcher(
     let shiftCallback = (n: any, o: any) => {
       shiftCallback = originalCallback
       // o is undefined on the first call
-      applyCb(n, isArray(n) ? [] : o)
+      return applyCb(n, isArray(n) ? [] : o)
     }
     callback = (n: any, o: any) => {
-      shiftCallback(n, o)
+      return shiftCallback(n, o)
     }
   }
 


Test patch:
diff --git a/test/apis/watch.spec.js b/test/apis/watch.spec.js
index eeb323e..ab946e1 100644
--- a/test/apis/watch.spec.js
+++ b/test/apis/watch.spec.js
@@ -1,7 +1,16 @@
 const Vue = require('vue/dist/vue.common.js')
-const { ref, reactive, watch, watchEffect, set } = require('../../src')
+const {
+  ref,
+  reactive,
+  watch,
+  watchEffect,
+  set,
+  nextTick,
+} = require('../../src')
+const { mockWarn } = require('../helpers')
 
 describe('api/watch', () => {
+  mockWarn(true)
   const anyFn = expect.any(Function)
   let spy
   beforeEach(() => {
@@ -587,6 +596,27 @@ describe('api/watch', () => {
       expect(spy).toHaveBeenNthCalledWith(2, [3, 1], [2, 1])
       expect(spy).toHaveBeenNthCalledWith(3, [3, 3], [3, 1])
     })
+
+    it('config.errorHandler should capture render errors', async () => {
+      new Vue({
+        setup() {
+          const a = ref(1)
+          watch(
+            a,
+            async () => {
+              throw new Error('userWatcherCallback error')
+            },
+            { immediate: true }
+          )
+          return {
+            a,
+          }
+        },
+        template: `<div>{{a}}</div>`,
+      }).$mount()
+      await nextTick()
+      expect(`userWatcherCallback error`).toHaveBeenWarned()
+    })
   })
 
   describe('Out of setup', () => {
diff --git a/test/helpers/mockWarn.ts b/test/helpers/mockWarn.ts
index 63d2372..500238b 100644
--- a/test/helpers/mockWarn.ts
+++ b/test/helpers/mockWarn.ts
@@ -15,7 +15,7 @@ export function mockWarn(asError = false) {
     toHaveBeenWarned(received: string) {
       asserted.add(received)
       const passed = warn.mock.calls.some(
-        (args) => args[0].indexOf(received) > -1
+        (args) => args[0].toString().indexOf(received) > -1
       )
       if (passed) {
         return {
@@ -91,7 +91,7 @@ export function mockWarn(asError = false) {
       .map((args) => args[0])
       .filter((received) => {
         return !assertedArray.some((assertedMsg) => {
-          return received.indexOf(assertedMsg) > -1
+          return received.toString().indexOf(assertedMsg) > -1
         })
       })
     warn.mockRestore()