Skip to content

Commit

Permalink
fix(errors): allow async errors to propagate
Browse files Browse the repository at this point in the history
Fix #576
  • Loading branch information
posva committed Jul 21, 2021
1 parent 041262b commit 17ee4e8
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 11 deletions.
69 changes: 69 additions & 0 deletions __tests__/actions.spec.ts
Expand Up @@ -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')
})
})
29 changes: 23 additions & 6 deletions src/store.ts
Expand Up @@ -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
}
Expand All @@ -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
}
}

Expand Down
15 changes: 10 additions & 5 deletions src/types.ts
Expand Up @@ -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]

Expand Down

0 comments on commit 17ee4e8

Please sign in to comment.