Catches errors and rejected promises, returns tuple with error and value.
npm install do-try-tuple
import doTry from 'do-try-tuple';
function div(a: number, b: number): number {
if (b !== 0) return a / b;
if (a !== 0) throw new Error(`Division by Zero`);
throw new Error('Indeterminate Form');
}
const [isDivOk, errX, x] = doTry(() => div(4, 2));
if (isDivOk) {
const doubleX = x * 2;
console.log('doubleX:', doubleX);
}
import doTry from 'do-try-tuple';
const [areUsersFetched, error, users] = await doTry(() => fetchUsers());
if (!areUsersFetched) {
console.error('Failed to fetch users:', error);
} else {
console.log('Users:', users);
}
or
import { safe } from 'do-try-tuple';
const [areUsersFetched, error, users] = await safe(fetchUsers());
if (!areUsersFetched) {
console.error('Failed to fetch users:', error);
} else {
console.log('Users:', users);
}
The library exports:
doTry
function (default export)safe
promise wrapper to make it resolving toErrValueTuple
Failure
,Success
andErrValueTuple
typesfailure
andsuccess
factory functionsisFailure
andisSuccess
type guards
takes a function that may throw an error or return a promise that may be rejected.
function (fn: () => never): readonly [false, unknown, never];
function (fn: () => Promise<never>): Promise<readonly [false, unknown, never]>;
function <T>(fn: () => Promise<T>): Promise<ErrValueTuple<T>>;
function <T>(fn: () => T): ErrValueTuple<T>;
is a function that wraps a promise and makes it resolving to ErrValueTuple
:
function safe<T>(promise: Promise<T>): Promise<ErrValueTuple<T>>;
It could be useful when you need to handle the promise rejection synchronously:
import { safe } from 'do-try-tuple';
const [areUsersFatched, error, users] = await safe(fetchUsers());
is a tuple representing the error case:
export type Failure<E = unknown> = readonly [ok: false, error: E, value: undefined];
The library respects the same motivation as caused introduction useUnknownInCatchVariables compiler option in TypeScript:
is a tuple representing the success case:
export type Success<T> = readonly [ok: true, error: undefined, value: T];
is a union of Failure<E>
and Success<T>
.
export type ErrValueTuple<T, E = unknown> = Failure<E> | Success<T>;
These functions allow to create ErrValueTuple
instances:
export function failure<E>(error: E): Failure<E>;
export function success<T>(value: T): Success<T>;
It could be useful in tests:
import { success, failure } from 'do-try-tuple';
test('div', () => {
expect(doTry(() => div(4, 2))).toEqual(success(2));
expect(doTry(() => div(4, 0))).toEqual(failure(new Error('Division by Zero')));
expect(doTry(() => div(0, 0))).toEqual(failure(new Error('Indeterminate Form')));
});
These functions allow to check if the value is Failure
or Success
:
export function isFailure(value: ErrValueTuple<unknown>): value is Failure;
export function isSuccess(value: ErrValueTuple<unknown>): value is Success<unknown>;
It allows to check the result and narrow the type without destructuring:
class DivError extends Error {
constructor(message: string) {
super(message);
this.name = 'DivError';
}
}
function divWithTypeError(a: number, b: number): ErrValueTuple<number, DivError> {
const result = doTry(() => div(a, b));
if (isSuccess(result)) return result;
return failure(new DivError('Failed to divide'));
}
You can map the result of doTry
applied to function returning a promise using then
method:
import doTry from 'do-try-tuple';
const [error, users] = await doTry(() => fetchUsers()).then(
([err, users]) => [err && new SomeCustomError(err), users] as const,
);
However, consider that functions returning promises can throw error synchronously:
const fetchUsers = (): Promise<string[]> => {
if (Math.random() < 0.5) throw new Error('Failed to fetch users');
return Promise.resolve(['Alice', 'Bob', 'Charlie']);
};
So, the doTry
in this case returns an ErrValueTuple
synchronously, and the
attempt to call then
method on it will throw an error:
TypeError: doTry(...).then is not a function
.
To handle this case, just add async
keyword before fn
argument:
const [error, users] = await doTry(async () => fetchUsers()).then(
([err, users]) => [err && new SomeCustomError(err), users] as const,
);
So, use
// CORRECT _____
const [err, value] = await doTry(async () => someFn(...))
.then(([err, value]) => {
// handle err and value
});
instead of
// WRONG ___________
const [err, value] = await doTry(/* async */() => someFn(...))
.then(([err, value]) => {
// handle err and value
});
The same is relevant for any other method of Promise
class, like catch
, finally
, etc.