Skip to content

Commit

Permalink
Adding guard (#277)
Browse files Browse the repository at this point in the history
* Adding guard

* Adding tests

* better guard

* running build

* running prettier

* add docs

* version bump
  • Loading branch information
manas-vessel committed Mar 28, 2023
1 parent 31c1397 commit 77cb220
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 3 deletions.
16 changes: 15 additions & 1 deletion cdn/radash.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,20 @@ const tryit = (func) => {
}
};
};
const guard = (func, shouldGuard) => {
const _guard = (err) => {
if (shouldGuard && !shouldGuard(err))
throw err;
return void 0;
};
const isPromise = (result) => result instanceof Promise;
try {
const result = func();
return isPromise(result) ? result.catch(_guard) : result;
} catch (err) {
return _guard(err);
}
};

const chain = (...funcs) => (...args) => {
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args));
Expand Down Expand Up @@ -874,4 +888,4 @@ const trim = (str, charsToTrim = " ") => {
return str.replace(regex, "");
};

export { alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };
export { alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, guard, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };
15 changes: 15 additions & 0 deletions cdn/radash.js
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,20 @@ var radash = (function (exports) {
}
};
};
const guard = (func, shouldGuard) => {
const _guard = (err) => {
if (shouldGuard && !shouldGuard(err))
throw err;
return void 0;
};
const isPromise = (result) => result instanceof Promise;
try {
const result = func();
return isPromise(result) ? result.catch(_guard) : result;
} catch (err) {
return _guard(err);
}
};

const chain = (...funcs) => (...args) => {
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args));
Expand Down Expand Up @@ -900,6 +914,7 @@ var radash = (function (exports) {
exports.fork = fork;
exports.get = get;
exports.group = group;
exports.guard = guard;
exports.intersects = intersects;
exports.invert = invert;
exports.isArray = isArray;
Expand Down
2 changes: 1 addition & 1 deletion cdn/radash.min.js

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions docs/async/guard.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: guard
group: 'Async'
description: Have a function return undefined if it errors out
---

## Basic usage

This lets you set a default value if an async function errors out.

```ts
const users = (await guard(fetchUsers)) ?? []
```

You can choose to guard only specific errors too

```ts
const isInvalidUserError = (err: any) => err.code === 'INVALID_ID'
const user = (await guard(fetchUser, isInvalidUserError)) ?? DEFAULT_USER
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "radash",
"version": "10.7.1",
"version": "10.8.1",
"description": "Functional utility library - modern, simple, typed, powerful",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.mjs",
Expand Down
26 changes: 26 additions & 0 deletions src/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,29 @@ export const tryit = <TFunction extends (...args: any) => any>(
}
}
}

/**
* A helper to try an async function that returns undefined
* if it fails.
*
* e.g. const result = await guard(fetchUsers)() ?? [];
*/
export const guard = <TFunction extends () => any>(
func: TFunction,
shouldGuard?: (err: any) => boolean
): ReturnType<TFunction> extends Promise<any>
? Promise<UnwrapPromisify<ReturnType<TFunction>> | undefined>
: ReturnType<TFunction> | undefined => {
const _guard = (err: any) => {
if (shouldGuard && !shouldGuard(err)) throw err
return undefined as any
}
const isPromise = (result: any): result is ReturnType<TFunction> =>
result instanceof Promise
try {
const result = func()
return isPromise(result) ? result.catch(_guard) : result
} catch (err) {
return _guard(err)
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export {
} from './array'
export {
defer,
guard,
map,
parallel,
reduce,
Expand Down
55 changes: 55 additions & 0 deletions src/tests/async.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,59 @@ describe('async module', () => {
assert.isAtLeast(diff, backoffs)
})
})

describe('_.guard', () => {
it('returns result of given async function', async () => {
const result = await _.guard(async () => {
return 'hello'
})
assert.equal(result, 'hello')
})
it('returns result of given sync function', async () => {
const result = _.guard(() => {
return 'hello'
})
assert.equal(result, 'hello')
})
it('returns error if given async function throws', async () => {
const result =
(await _.guard(async () => {
throw new Error('error')
})) ?? 'good-bye'
assert.equal(result, 'good-bye')
})
it('returns error if given sync function throws', async () => {
const alwaysThrow = () => {
if (1 > 0) throw new Error('error')
return undefined
}
const result = _.guard(alwaysThrow) ?? 'good-bye'
assert.equal(result, 'good-bye')
})
it('throws error if shouldGuard returns false', async () => {
const makeFetchUser = (id: number) => {
return async () => {
if (id === 1) return 'user1'
if (id === 2) throw new Error('user not found')
throw new Error('unknown error')
}
}
const isUserNotFoundErr = (err: any) => err.message === 'user not found'
const fetchUser = async (id: number) =>
(await _.guard(makeFetchUser(id), isUserNotFoundErr)) ?? 'default-user'

const user1 = await fetchUser(1)
assert.equal(user1, 'user1')

const user2 = await fetchUser(2)
assert.equal(user2, 'default-user')

try {
await fetchUser(3)
assert.fail()
} catch (err: any) {
assert.equal(err.message, 'unknown error')
}
})
})
})

1 comment on commit 77cb220

@vercel
Copy link

@vercel vercel bot commented on 77cb220 Mar 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.