]> git.ipfire.org Git - thirdparty/vuejs/pinia.git/commitdiff
fix(errors): allow async errors to propagate
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 21 Jul 2021 08:29:57 +0000 (10:29 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 21 Jul 2021 08:29:57 +0000 (10:29 +0200)
Fix #576

__tests__/actions.spec.ts
src/store.ts
src/types.ts

index 58c5bcdd08c886c840efcfdfe045edbaa43672b4..ecd507ab14769c1aa1a70f531e549935fda70385 100644 (file)
@@ -137,9 +137,78 @@ describe('Actions', () => {
     expect(() => store.throws()).toThrowError('fail')
   })
 
+  it('throws errors', () => {
+    const store = useStore()
+    expect(() => store.throws()).toThrowError('fail')
+  })
+
+  it('can avoid errors to propagate', () => {
+    const store = useStore()
+    store.$onAction(({ onError }) => {
+      onError(() => false)
+    })
+    expect(() => store.throws()).not.toThrowError('fail')
+  })
+
+  it('can avoid async errors to propagate', async () => {
+    const store = useStore()
+    store.$onAction(({ onError }) => {
+      onError(() => false)
+    })
+    await expect(store.rejects()).resolves.toBe(undefined)
+  })
+
   it('throws async errors', async () => {
     const store = useStore()
     expect.assertions(1)
     await expect(store.rejects()).rejects.toBe('fail')
   })
+
+  it('can catch async errors', async () => {
+    const store = useStore()
+    expect.assertions(3)
+    const spy = jest.fn()
+    await expect(store.rejects().catch(spy)).resolves.toBe(undefined)
+    expect(spy).toHaveBeenCalledTimes(1)
+    expect(spy).toHaveBeenCalledWith('fail')
+  })
+
+  it('can override the returned value', () => {
+    const store = useStore()
+    expect.assertions(2)
+    store.$onAction(({ after }) => {
+      // @ts-expect-error: cannot return a string because no action returns a string
+      after((v) => {
+        expect(v).toBe(false)
+        return 'hello'
+      })
+    })
+    expect(store.toggle()).toBe('hello')
+  })
+
+  it('can override the resolved value', async () => {
+    const store = useStore()
+    expect.assertions(2)
+    store.$onAction(({ after }) => {
+      // @ts-expect-error: cannot return a string because no action returns a string
+      after((v) => {
+        expect(v).toBe(false)
+        return 'hello'
+      })
+    })
+    await expect(store.getNonA()).resolves.toBe('hello')
+  })
+
+  it('can override the resolved value with a promise', async () => {
+    const store = useStore()
+    expect.assertions(2)
+    store.$onAction(({ after }) => {
+      // @ts-expect-error: cannot return a string because no action returns a string
+      after(async (v) => {
+        expect(v).toBe(false)
+        return 'hello'
+      })
+    })
+    await expect(store.getNonA()).resolves.toBe('hello')
+  })
 })
index d2415e2f31231b4678404b50ee09d064f5108ad2..bd7a72cadfb1c91d10fec562cab21d10943544d2 100644 (file)
@@ -330,8 +330,8 @@ function createSetupStore<
       setActivePinia(pinia)
       const args = Array.from(arguments)
 
-      let afterCallback: (resolvedReturn: any) => void = noop
-      let onErrorCallback: (error: unknown) => void = noop
+      let afterCallback: (resolvedReturn: any) => any = noop
+      let onErrorCallback: (error: unknown) => unknown = noop
       function after(callback: typeof afterCallback) {
         afterCallback = callback
       }
@@ -353,13 +353,30 @@ function createSetupStore<
       let ret: any
       try {
         ret = action.apply(this || store, args)
-        Promise.resolve(ret).then(afterCallback).catch(onErrorCallback)
+        // Promise.resolve(ret).then(afterCallback).catch(onErrorCallback)
       } catch (error) {
-        onErrorCallback(error)
-        throw error
+        if (onErrorCallback(error) !== false) {
+          throw error
+        }
       }
 
-      return ret
+      if (ret instanceof Promise) {
+        return ret
+          .then((value) => {
+            const newRet = afterCallback(value)
+            // allow the afterCallback to override the return value
+            return newRet === undefined ? value : newRet
+          })
+          .catch((error) => {
+            if (onErrorCallback(error) !== false) {
+              return Promise.reject(error)
+            }
+          })
+      }
+
+      const newRet = afterCallback(ret)
+
+      return newRet === undefined ? ret : newRet
     }
   }
 
index 6c665a71225dd15ae88ff75133cedc41c32f4034..418d65c83d2dce9e0df224e43cfb618fdaf92fae 100644 (file)
@@ -183,19 +183,24 @@ export type StoreOnActionListenerContext<
     args: A[Name] extends _Method ? Parameters<A[Name]> : unknown[]
 
     /**
-     * Sets up a hook once the action is finished. It receives the return value of
-     * the action, if it's a Promise, it will be unwrapped.
+     * Sets up a hook once the action is finished. It receives the return value
+     * of the action, if it's a Promise, it will be unwrapped. Can return a
+     * value (other than `undefined`) to **override** the returned value.
      */
     after: (
       callback: A[Name] extends _Method
-        ? (resolvedReturn: UnwrapPromise<ReturnType<A[Name]>>) => void
+        ? (
+            resolvedReturn: UnwrapPromise<ReturnType<A[Name]>>
+            // allow the after callback to override the return value
+          ) => void | ReturnType<A[Name]> | UnwrapPromise<ReturnType<A[Name]>>
         : () => void
     ) => void
 
     /**
-     * Sets up a hook if the action fails.
+     * Sets up a hook if the action fails. Return `false` to catch the error and
+     * stop it fro propagating.
      */
-    onError: (callback: (error: unknown) => void) => void
+    onError: (callback: (error: unknown) => unknown | false) => void
   }
 }[keyof A]