Simple try/catch wrappers that always return a Result discriminated union, plus ready-made helpers (ok, err) for predictable control flow.
- Turn any sync or async function into an explicit
Resultobject with zero dependencies. - Strong TypeScript types guide your control flow (
failsand tagged errors). - Optional failure handlers let you log, meter, or mutate state exactly where the error occurs.
npm install @zokugun/xtryimport { xatry } from '@zokugun/xtry'
const userResult = await xatry(fetchUserFromApi());
if(userResult.fails) {
console.error(userResult.error);
return;
}
console.log('User loaded:', userResult.value);import { err, type Result, xatry, xtry } from '@zokugun/xtry'
export type FoobarError = { type: 'FOOBAR'; message: string };
async function foobar(): Result<number, FoobarError> {
const result = await xatry(fetchUserFromApi());
if(fails) {
return err({ type: 'FOOBAR', message: 'The promise has failed...' });
}
return try(() => calculateAge(result.value));
}
async function main() {
const result = await foobar();
if(result.fails) {
console.error(result.error.message);
return;
}
console.log(result.value);
}YResult extends the base Result union with a success flag so you can distinguish "valid failure" states from true errors.
import { err, ok, yerr, yok, type YResult } from '@zokugun/xtry'
function toNumber(input: string): YResult<number, MyError, 'empty-string'> {
if(input.length > 0) {
return yerr('empty-string');
}
const floatValue = Number.parseFloat(input);
if(Number.isNaN(floatValue)) {
return err({ type: '#VALUE!' });
}
return yok(floatValue);
}
function add(_x: string, _y: number): Result<number, MyError> {
const x = toNumber(_x);
if(x.fails) {
return x;
}
if(!x.success) {
return ok(0);
}
const y = toNumber(_y);
if(y.fails) {
return y;
}
if(!y.success) {
return ok(0);
}
return x.value + y.value;
}export type Success<T> = { fails: false; value: T; error: null };
export type Failure<E> = { fails: true; value: null; error: E };
export type Result<T, E> = Success<T> | Failure<E>;
export function ok<T>(value: T): Success<T>;
export function err<E>(error: E): Failure<E>;export function xtry<T, E>(func: () => Exclude<T, Promise<unknown>>, handler?: (error: unknown) => void | E): Result<T, E>;
export function xatry<T, E>(func: (() => Exclude<T, Promise<unknown>>) | Promise<Exclude<T, Promise<unknown>>>, handler?: (error: unknown) => void | E): Promise<Result<T, E>>;
export function stringifyError(error: unknown): string;Both helpers:
- execute the supplied function and capture thrown values;
- call the optional
handlerbefore turning that value intoerr(error); - never throw, making the return signature a reliable discriminated union.
export type YSuccess<T> = Success<T> & { success: true };
export type YFailure<S> = { fails: false; success: false; type: S; value: null; error: null };
export type YResult<T, E, S> = Failure<E> | YSuccess<T> | YFailure<S>;
export function yok<T>(value: T): YSuccess<T>;
export function yerr<S>(type: S): YFailure<S>;These helpers are useful when you need to separate soft rejections (success: false) from hard failures (fails: true).
- Narrow on
failsfirst, then use other flags (success, customtypeorvalue) for the happy-path branching.
Support this project by becoming a financial contributor.
| ko-fi.com/daiyam | |
| liberapay.com/daiyam/donate | |
| paypal.me/daiyam99 |
Copyright © 2025-present Baptiste Augrain
Licensed under the MIT license.