Skip to content

Commit 77cb220

Browse files
authored
Adding guard (#277)
* Adding guard * Adding tests * better guard * running build * running prettier * add docs * version bump
1 parent 31c1397 commit 77cb220

File tree

8 files changed

+134
-3
lines changed

8 files changed

+134
-3
lines changed

cdn/radash.esm.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,20 @@ const tryit = (func) => {
441441
}
442442
};
443443
};
444+
const guard = (func, shouldGuard) => {
445+
const _guard = (err) => {
446+
if (shouldGuard && !shouldGuard(err))
447+
throw err;
448+
return void 0;
449+
};
450+
const isPromise = (result) => result instanceof Promise;
451+
try {
452+
const result = func();
453+
return isPromise(result) ? result.catch(_guard) : result;
454+
} catch (err) {
455+
return _guard(err);
456+
}
457+
};
444458

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

877-
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 };
891+
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 };

cdn/radash.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,20 @@ var radash = (function (exports) {
444444
}
445445
};
446446
};
447+
const guard = (func, shouldGuard) => {
448+
const _guard = (err) => {
449+
if (shouldGuard && !shouldGuard(err))
450+
throw err;
451+
return void 0;
452+
};
453+
const isPromise = (result) => result instanceof Promise;
454+
try {
455+
const result = func();
456+
return isPromise(result) ? result.catch(_guard) : result;
457+
} catch (err) {
458+
return _guard(err);
459+
}
460+
};
447461

448462
const chain = (...funcs) => (...args) => {
449463
return funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args));
@@ -900,6 +914,7 @@ var radash = (function (exports) {
900914
exports.fork = fork;
901915
exports.get = get;
902916
exports.group = group;
917+
exports.guard = guard;
903918
exports.intersects = intersects;
904919
exports.invert = invert;
905920
exports.isArray = isArray;

cdn/radash.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/async/guard.mdx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
title: guard
3+
group: 'Async'
4+
description: Have a function return undefined if it errors out
5+
---
6+
7+
## Basic usage
8+
9+
This lets you set a default value if an async function errors out.
10+
11+
```ts
12+
const users = (await guard(fetchUsers)) ?? []
13+
```
14+
15+
You can choose to guard only specific errors too
16+
17+
```ts
18+
const isInvalidUserError = (err: any) => err.code === 'INVALID_ID'
19+
const user = (await guard(fetchUser, isInvalidUserError)) ?? DEFAULT_USER
20+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "radash",
3-
"version": "10.7.1",
3+
"version": "10.8.1",
44
"description": "Functional utility library - modern, simple, typed, powerful",
55
"main": "dist/cjs/index.cjs",
66
"module": "dist/esm/index.mjs",

src/async.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,29 @@ export const tryit = <TFunction extends (...args: any) => any>(
207207
}
208208
}
209209
}
210+
211+
/**
212+
* A helper to try an async function that returns undefined
213+
* if it fails.
214+
*
215+
* e.g. const result = await guard(fetchUsers)() ?? [];
216+
*/
217+
export const guard = <TFunction extends () => any>(
218+
func: TFunction,
219+
shouldGuard?: (err: any) => boolean
220+
): ReturnType<TFunction> extends Promise<any>
221+
? Promise<UnwrapPromisify<ReturnType<TFunction>> | undefined>
222+
: ReturnType<TFunction> | undefined => {
223+
const _guard = (err: any) => {
224+
if (shouldGuard && !shouldGuard(err)) throw err
225+
return undefined as any
226+
}
227+
const isPromise = (result: any): result is ReturnType<TFunction> =>
228+
result instanceof Promise
229+
try {
230+
const result = func()
231+
return isPromise(result) ? result.catch(_guard) : result
232+
} catch (err) {
233+
return _guard(err)
234+
}
235+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export {
3131
} from './array'
3232
export {
3333
defer,
34+
guard,
3435
map,
3536
parallel,
3637
reduce,

src/tests/async.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,4 +430,59 @@ describe('async module', () => {
430430
assert.isAtLeast(diff, backoffs)
431431
})
432432
})
433+
434+
describe('_.guard', () => {
435+
it('returns result of given async function', async () => {
436+
const result = await _.guard(async () => {
437+
return 'hello'
438+
})
439+
assert.equal(result, 'hello')
440+
})
441+
it('returns result of given sync function', async () => {
442+
const result = _.guard(() => {
443+
return 'hello'
444+
})
445+
assert.equal(result, 'hello')
446+
})
447+
it('returns error if given async function throws', async () => {
448+
const result =
449+
(await _.guard(async () => {
450+
throw new Error('error')
451+
})) ?? 'good-bye'
452+
assert.equal(result, 'good-bye')
453+
})
454+
it('returns error if given sync function throws', async () => {
455+
const alwaysThrow = () => {
456+
if (1 > 0) throw new Error('error')
457+
return undefined
458+
}
459+
const result = _.guard(alwaysThrow) ?? 'good-bye'
460+
assert.equal(result, 'good-bye')
461+
})
462+
it('throws error if shouldGuard returns false', async () => {
463+
const makeFetchUser = (id: number) => {
464+
return async () => {
465+
if (id === 1) return 'user1'
466+
if (id === 2) throw new Error('user not found')
467+
throw new Error('unknown error')
468+
}
469+
}
470+
const isUserNotFoundErr = (err: any) => err.message === 'user not found'
471+
const fetchUser = async (id: number) =>
472+
(await _.guard(makeFetchUser(id), isUserNotFoundErr)) ?? 'default-user'
473+
474+
const user1 = await fetchUser(1)
475+
assert.equal(user1, 'user1')
476+
477+
const user2 = await fetchUser(2)
478+
assert.equal(user2, 'default-user')
479+
480+
try {
481+
await fetchUser(3)
482+
assert.fail()
483+
} catch (err: any) {
484+
assert.equal(err.message, 'unknown error')
485+
}
486+
})
487+
})
433488
})

0 commit comments

Comments
 (0)