From: Eduardo San Martin Morote Date: Wed, 21 Jul 2021 08:29:57 +0000 (+0200) Subject: fix(errors): allow async errors to propagate X-Git-Tag: v2.0.0-rc.0~39 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=17ee4e85fb2c084ba27730dae4f21683686156c6;p=thirdparty%2Fvuejs%2Fpinia.git fix(errors): allow async errors to propagate Fix #576 --- diff --git a/__tests__/actions.spec.ts b/__tests__/actions.spec.ts index 58c5bcdd..ecd507ab 100644 --- a/__tests__/actions.spec.ts +++ b/__tests__/actions.spec.ts @@ -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') + }) }) diff --git a/src/store.ts b/src/store.ts index d2415e2f..bd7a72ca 100644 --- a/src/store.ts +++ b/src/store.ts @@ -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 } } diff --git a/src/types.ts b/src/types.ts index 6c665a71..418d65c8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -183,19 +183,24 @@ export type StoreOnActionListenerContext< args: A[Name] extends _Method ? Parameters : 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>) => void + ? ( + resolvedReturn: UnwrapPromise> + // allow the after callback to override the return value + ) => void | ReturnType | UnwrapPromise> : () => 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]