tryCatch
EditRuns synchronous or asynchronous work and returns an error-first result tuple.
export type TryCatchResult<Value> = | [error: Error, value: undefined] | [error: undefined, value: Value];
type IsAny<Value> = 0 extends 1 & Value ? true : false;type IsUnknown<Value> = IsAny<Value> extends true ? false : unknown extends Value ? keyof Value extends never ? true : false : false;type UncertainTryCatchReturn<Return> = | TryCatchResult<Return> | Promise<TryCatchResult<unknown>>;
/** * Objects and unknown values retain an uncertain Promise branch because * JavaScript thenability is a runtime property that their static type may hide. */export type TryCatchReturn<Return> = IsAny<Return> extends true ? UncertainTryCatchReturn<Return> : IsUnknown<Return> extends true ? UncertainTryCatchReturn<Return> : [void] extends [Return] ? UncertainTryCatchReturn<unknown> : object extends Return ? UncertainTryCatchReturn<Return> : [Return] extends [never] ? TryCatchResult<never> : Return extends PromiseLike<unknown> ? Promise<TryCatchResult<Awaited<Return>>> : Return extends object ? UncertainTryCatchReturn<Return> : TryCatchResult<Return>;
const toError = (reason: unknown): Error => { if (reason instanceof Error) return reason; return new Error("Operation failed", { cause: reason });};
type Then = ( this: object, onfulfilled: (value: unknown) => void, onrejected: (reason: unknown) => void) => unknown;
type ThenInspection = | { status: "not-thenable" } | { status: "thenable"; receiver: object; then: Then } | { status: "threw"; reason: unknown };
const inspectThen = (value: unknown): ThenInspection => { if ( (typeof value !== "object" || value === null) && typeof value !== "function" ) { return { status: "not-thenable" }; }
try { const then = (value as { then?: unknown }).then; return typeof then === "function" ? { status: "thenable", receiver: value, then: then as Then } : { status: "not-thenable" }; } catch (reason) { return { status: "threw", reason }; }};
const assimilate = ( receiver: object, then: Then): Promise<unknown> => { return new Promise((resolve, reject) => { queueMicrotask(() => { try { Reflect.apply(then, receiver, [ (value: unknown) => { if (value === receiver) { reject(new TypeError("Thenable cannot resolve to itself")); return; } resolve(value); }, reject, ]); } catch (reason) { reject(reason); } }); });};
/** Executes synchronous or asynchronous work as an error-first result tuple. */export const tryCatch = <Return>( operation: (this: void) => Return): TryCatchReturn<Return> => { try { const value = operation(); const inspection = inspectThen(value);
if (inspection.status === "thenable") { return assimilate(inspection.receiver, inspection.then).then( (resolved) => [undefined, resolved], (reason) => [toError(reason), undefined] ) as TryCatchReturn<Return>; }
if (inspection.status === "threw") { return Promise.resolve([ toError(inspection.reason), undefined, ]) as TryCatchReturn<Return>; }
return [undefined, value] as TryCatchReturn<Return>; } catch (reason) { return [toError(reason), undefined] as TryCatchReturn<Return>; }};Download
Section titled “Download”wget -O src/lib/tryCatch.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/functions/tryCatch.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/functions "$temp_dir" && mv "$temp_dir/tryCatch.ts" src/lib/tryCatch.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { tryCatch } from "./tryCatch";
const result = tryCatch( (): { ready: boolean } => JSON.parse('{"ready":true}'));const [error, value] = result instanceof Promise ? await result : result;
if (error) { console.error(error);} else { console.log(value);}import { tryCatch } from "./tryCatch";
const result = tryCatch( (): unknown => JSON.parse("invalid json"));const [error] = result instanceof Promise ? await result : result;
if (error) console.error("Could not parse the value", error);import { tryCatch } from "./tryCatch";
const [error, response] = await tryCatch(() => fetch("/api/profile"));
if (error) { console.error(error);} else { console.log(await response.json());}import { tryCatch } from "./tryCatch";
const [error] = await tryCatch(async () => { throw new Error("Profile request failed");});
if (error) console.error(error.message);operation(() => T | PromiseLike<T>) — Synchronous or asynchronous work to invoke once.
Returns
Section titled “Returns”- Synchronous operation:
[Error, undefined] | [undefined, T] - Promise or thenable operation:
Promise<[Error, undefined] | [undefined, Awaited<T>]>
Existing Error instances are preserved. Other thrown or rejected values are wrapped in an Error and retained as its cause.
Thenables are assimilated from a single then lookup with their receiver preserved.
Object-compatible broad types, unknown, and void include both synchronous
and Promise branches. Thenability can only be determined at runtime, and
TypeScript permits a value-returning function where a void callback is
expected.