do-try-tuple
Catches errors and rejected promises, returns tuple with error and value.
Installation
npm install do-try-tuple
Usage
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 [errX, x] = doTry(() => div(4, 2));
if (errX == null) {
const doubleX = x * 2;
console.log('doubleX:', doubleX);
}
Async Usage
import doTry from 'do-try-tuple';
const [error, users] = await doTry(() => fetchUsers());
if (error != null) {
console.error('Failed to fetch users:', error);
} else {
console.log('Users:', users);
}
API
The library exports:
doTry
function (default export)ErrValueTuple
typeUnknownError
typeDoTryError
classDoTryErrorCode
type and constants
doTry
function
takes a function that may throw an error or return a promise that may be rejected.
function doTry(fn: () => never): [UnknownError, never];
function doTry(fn: () => Promise<never>): Promise<[UnknownError, never]>;
function doTry<T>(fn: () => T): ErrValueTuple<T>;
function doTry<T>(fn: () => Promise<T>): Promise<ErrValueTuple<T>>;
ErrValueTuple
type
is a union of two tuples: one representing the error case and the other
representing the success case.
export type ErrValueTuple<T> =
| [UnknownError, undefined]
| [undefined, T];
UnknownError
type
type represents an unknown, non-nullish value caught by doTry
function.
export type UnknownError = NonNullable<unknown>;
In case when doTry
catches null
or undefined
value, it returns a DoTryError
as the first item of the tuple.
The library respects the same motivation as caused introduction
useUnknownInCatchVariables
compiler option in TypeScript:
- we cannot be sure that all thrown errors are instances of
Error
class
DoTryError
class
is an error class that is returned when doTry
cannot comply to the ErrValueTuple
contract:
- when the
fn
argument is not a function - when the thrown and caught error is
null
or undefined
- when the promise is rejected with
null
or undefined
value
export class DoTryError extends Error {
constructor(code: DoTryErrorCode, cause: unknown);
}
Fields:
Field | Type | Description |
---|
message | string | error message |
code | DoTryErrorCode | error code |
cause | unknown | caught error (null or undefined ) or fn argument |
DoTryErrorCode
type and constants
is an union of string literal error codes that DoTryError
class uses.
Constant | Description | Cause |
---|
ERR_NOT_A_FUNCTION | fn argument is not a function | fn |
ERR_NULLISH_VALUE_REJECTED | promise is rejected with null or undefined value | caught value |
ERR_NULLISH_VALUE_THROWN | fn throws null or undefined value | caught value |
Important! Discriminating ErrValueTuple
To discriminate the ErrValueTuple
type, you should compare the first element of the tuple to
undefined
. The most concise way to do that is to use the != null
expression:
const [err, value] = doTry(() => someFn(...someArgs));
if (err != null) {
return;
}
The most performant way is to use strict equality comparison:
const [err, value] = doTry(() => someFn(...someArgs));
if (err !== undefined) {
return;
}
Do not use if (error)
or if (!error)
to discriminate the tuple
The if (error)
expression will not work as expected in else
block, because if
casts
err
to boolean
type and narrows its type in then
-branch correctly, but in else
-branch
the type of err
is still UnknownError | undefined
, so TypeScript
cannot discriminate
the ErrValueTuple
type correctly:
const [error, value] = doTry(() => someFn(...someArgs));
if (error) {
return;
}
Using doTry().then()
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
const [err, value] = await doTry(async () => someFn(...))
.then(([err, value]) => {
});
instead of
const [err, value] = await doTry(() => someFn(...))
.then(([err, value]) => {
});
The same is relevant for any other method of Promise
class, like catch
, finally
, etc.