Skip to content

Commit

Permalink
Merge pull request #3 from Vehmloewff/error-recovery-utils
Browse files Browse the repository at this point in the history
feat(errors): bindErrorRecovery, withErrorRecovery, withAsyncErrorRecovery
  • Loading branch information
Vehmloewff authored Sep 15, 2023
2 parents b6f3c36 + cec9141 commit 9e1f6eb
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
62 changes: 62 additions & 0 deletions errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { asserts } from './deps.ts'
import { bindErrorRecovery, withAsyncErrorRecovery, withErrorRecovery } from './errors.ts'

Deno.test('bindErrorRecovery works', () => {
function foo(error: boolean) {
if (error) throw new Error('I throw')

return 12
}

const safeFoo = bindErrorRecovery(foo, 20)

asserts.assertThrows(() => foo(true))
asserts.assertEquals(safeFoo(true), 20)
asserts.assertEquals(safeFoo(false), 12)
})

Deno.test('bindErrorRecovery works on promises', async () => {
async function foo(error: boolean) {
await new Promise((resolve) => setTimeout(resolve, 5))

if (error) throw new Error('I throw')
return 12
}

const safeFoo = bindErrorRecovery(foo, 20)

asserts.assertRejects(async () => await foo(true))
asserts.assertEquals(await safeFoo(true), 20)
asserts.assertEquals(await safeFoo(false), 12)
})

Deno.test('withErrorRecovery works', () => {
asserts.assertEquals(withErrorRecovery(() => 12, 13), 12)

asserts.assertEquals(
withErrorRecovery(() => {
throw new Error('error')
}, 13),
13,
)
})

Deno.test('withAsyncErrorRecovery works', async () => {
asserts.assertEquals(
await withAsyncErrorRecovery(async () => {
await new Promise((resolve) => setTimeout(resolve, 5))

return 12
}, 13),
12,
)

asserts.assertEquals(
await withAsyncErrorRecovery(async () => {
await new Promise((resolve) => setTimeout(resolve, 5))

throw new Error('error')
}, 13),
13,
)
})
59 changes: 59 additions & 0 deletions errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,62 @@ export function errorFromResponse(status: number, text: string): Error {

return new Error(text)
}

/**
* Bind error recovery to `fn`, which is expected to be a function. If when the function is later called and errors,
* `recoverWith` will be returned instead.
*
* Examples:
*
* ```ts
* function foo() {
* throw new Error('I throw')
* }
*
* const safeFoo = bindErrorRecovery(foo, null)
*
* foo() // Error: I throw
* safeFoo() // null
* ```
*/
export function bindErrorRecovery<
Args extends unknown[],
Return extends unknown,
O,
>(fn: (...args: Args) => Return, recoverWith: O): (...args: Args) => Return | O {
if (typeof fn !== 'function') throw new Error('Cannot bind error recovery to a value that is not a function')

// @ts-ignore returned function will match `fn`
return (...args) => {
try {
const res = fn(...args)
if (res instanceof Promise) return res.catch(() => recoverWith)

return res
} catch (_) {
return recoverWith
}
}
}

/**
* Call `fn`, returning its result, but return `recoverWith` if it errors. If `fn` returns a promise,
* use `withAsyncErrorRecovery` */
export function withErrorRecovery<T, O>(fn: () => T, recoverWith: O): T | O {
try {
return fn()
} catch (_) {
return recoverWith
}
}

/**
* Call `fn`, returning its result, but return `recoverWith` if it errors. If `fn` doesn't return a promise,
* use `withErrorRecovery` instead */
export async function withAsyncErrorRecovery<T, O>(fn: () => Promise<T>, recoverWith: O): Promise<T | O> {
try {
return await fn()
} catch (_) {
return recoverWith
}
}

0 comments on commit 9e1f6eb

Please sign in to comment.