@zokugun/xtry
v0.11.1
Published
simple try/catch wrapper returning Result
Readme
@zokugun/xtry
Simple try/catch wrappers that always return a Result discriminated union, plus ready-made helpers (ok, err) for predictable control flow.
Why @zokugun/xtry?
- 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.
Installation
npm install @zokugun/xtryQuick Start
import { xtry } from '@zokugun/xtry'
const userResult = await xtry(fetchUserFromApi());
if(userResult.fails) {
console.error(userResult.error);
return;
}
console.log('User loaded:', userResult.value);Advanced Example
import { err, type Result, xtry } from '@zokugun/xtry'
export type FoobarError = { type: 'FOOBAR'; message: string };
async function foobar(): Result<number, FoobarError> {
const result = await xtry(fetchUserFromApi());
if(fails) {
return err({ type: 'FOOBAR', message: 'The promise has failed...' });
}
return xtry(() => calculateAge(result.value));
}
async function main() {
const result = await foobar();
if(result.fails) {
console.error(result.error.message);
return;
}
console.log(result.value);
}Partial Example
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;
}Tips
- Narrow on
failsfirst, then use other flags (success, custommiscueorvalue) for the happy-path branching.
API reference
Result helpers
type Success<T> = { fails: false; value: T; error: undefined };
type Failure<E> = { fails: true; value: undefined; error: E };
type Result<T, E> = Success<T> | Failure<E>;
function ok<T>(value?: T): Success<T>;
function err<E>(error: E): Failure<E>;Pre-built ok constants
To minimize allocations when returning the same Success shape, you can reuse the exported frozen helpers:
| Constant | Wrapped value | Type | Typical usage |
| -------------- | --------------- | -------------------- | ------------------------------------------------------ |
| OK | ok() | Success<void> | Generic void success (e.g., cleanup, notifications) |
| OK_NULL | ok(null) | Success<null> | APIs that explicitly signal "nothing" with null |
| OK_UNDEFINED | ok(undefined) | Success<undefined> | APIs that explicitly signal "nothing" with undefined |
| OK_TRUE | ok(true) | Success<true> | Flag-style functions (enable() / disable()) |
| OK_FALSE | ok(false) | Success<false> | Guard checks that succeed with false |
Try helpers
function xtry<T, E>(func: (() => MaybePromise<T>) | Promise<T>, handler?: (error: unknown) => void | E): MaybePromise<Result<T, E>>;
function xtry<T, E>(iterable: (() => Iterable<T>) | Iterable<T>, handler?: (error: unknown) => void | E): Iterable<Result<T, E>>;
function xtry<T, E>(iterable: (() => AsyncIterable<T>) | AsyncIterable<T>, handler?: (error: unknown) => void | E): AsyncIterable<Result<T, E>>;
function xtryAsync<T, E>(func: (() => Promise<T>) | Promise<T>, handler?: (error: unknown) => void | E): Promise<Result<T, E>>;
function xtryAsyncIterable<T, E>(iterable: (() => MaybePromise<AsyncIterable<T>>) | MaybePromise<AsyncIterable<T>>, handler?: (error: unknown) => void | E): AsyncIterable<Result<T, E>>;
function xtrySync<T, E>(func: () => Exclude<T, Promise<unknown>>, handler?: (error: unknown) => void | E): Result<T, E>;
function xtrySyncIterable<T, E>(iterable: (() => Iterable<T>) | Iterable<T>, handler?: (error: unknown) => void | E): Iterable<Result<T, E>>;
(error: unknown) => void | E): AsyncIterable<Result<T, E>>;
function stringifyError(error: unknown): string;xtry inspects the supplied value and chooses the matching shape:
- promises or async factories →
Promise<Result<…>> - async iterables →
AsyncIterable<Result<…>> - iterators/generators →
Iterable<Result<…>> - everything else → plain
Result<…>
Wrap default collections (arrays, sets, maps, strings, typed arrays, …) inside a function if you need them treated as raw values instead of iterables.
Reach for xtrySync/xtryAsync when you want to force a specific mode, and prefer the explicit xtrySyncIterable/xtryAsyncIterable exports whenever you always expect iterable outputs.
xtryIterable and xtryAsyncIterable wrap synchronous or asynchronous iterables, yielding a stream of Result values and emitting a single err(...) before stopping when the underlying iterator throws or rejects.
All helpers:
- execute the supplied function and capture thrown values;
- call the optional
handlerbefore turning that value intoerr(error);
xtryify helpers
xtryify* helpers turn any function into a reusable wrapper that always yields a Result, saving you from retyping xtry(…) every time you call it.
import { xtryifyAsync, xtryifySync } from '@zokugun/xtry'
const fetchUserSafely = xtryifyAsync((id: string) => fetch(`/users/${id}`).then(r => r.json()));
const parseConfig = xtryifySync(() => JSON.parse(readFileSync('config.json', 'utf8')));
const userResult = await fetchUserSafely('42');
const configResult = parseConfig();Available variants mirror the regular helpers:
xtryifySync(fn)andxtryifyAsync(fn)return new functions that forward arguments tofnand capture thrown/errors asResultobjects.xtryifySyncIterable(fn)andxtryifyAsyncIterable(fn)wrap functions that return iterables/async iterables, yielding streams ofResultvalues.
Because the returned function already encapsulates the try/catch logic, you can share it across modules (e.g., inject into DI containers or export once for common utilities) while keeping strong Result typing for every call site.
Partial helpers
type YSuccess<T> = Success<T> & { success: true };
type YFailure<M> = { fails: false; success: false; miscue: M; value: undefined; error: undefined };
type YResult<T, E, M> = Failure<E> | YSuccess<T> | YFailure<M>;
function yok<T>(value: T): YSuccess<T>;
function yerr<M>(type: M): YFailure<M>;
function yres<T, E>(result: MaybePromise<Result<T, E>>): MaybePromise<Failure<E> | YSuccess<T>>;
function yresSync<T, E>(result: Result<T, E>): Failure<E> | YSuccess<T>;
function yresAsync<T, E>(promise: Promise<Result<T, E>>): Promise<Failure<E> | YSuccess<T>>;
function yep<T>(result: Success<T>): YSuccess<T>;These helpers are useful when you need to separate soft rejections (success: false) from hard failures (fails: true).
Defer helpers
type DeferSync<E> = (result?: Result<unknown, E>) => Result<unknown, E> | Success<void>;
type DeferAsync<E> = (result?: Result<unknown, E>) => Promise<Result<unknown, E> | Success<void>>;
function xdefer<E>(callback: () => Result<unknown, E> | Promise<Result<unknown, E>>): DeferSync<E> | DeferAsync<E>;
function xdeferSync<E>(callback: () => Result<unknown, E>): DeferSync<E>;
function xdeferAsync<E>(callback: (() => Promise<Result<unknown, E>>) | Promise<Result<unknown, E>>): DeferAsync<E>;Use these helpers to express "finally" logic that can also fail while preserving the original result when needed:
import { stringifyError, xdefer, xtry } from '@zokugun/xtry/async'
function test(): Result<void, string> {
const closeConnection = xdefer(xtry(connection.close()));
const queryResult = await xtry(connection.query('SELECT 1'));
if(queryResult.fails) {
return closeConnection(err(stringifyError(queryResult.error)))
}
...
return closeConnection();
}xdeferinspects the callback result: if it fails, it becomes the returned error unless the main result already failed.- Passing a promise (or async factory) makes the defer helper async-aware;
xdeferSync/xdeferAsynclet you pin the behavior explicitly for bundlers. - Calling the returned function with no arguments just runs the deferred work and yields
ok().
Module entry points
Choose the entry point that matches your environment and naming preferences:
| Import path | Description | xtry name | xdefer name | Extra alias |
| --------------------- | --------------------------- | ------------------------------- | ------------------------------------- | ------------------------------- |
| @zokugun/xtry | Both sync, async and hybrid | xtry, xtryAsync, xtrySync | xdefer, xdeferAsync, xdeferSync | yres, yresAsync, yresSync |
| @zokugun/xtry/async | Async-only | xtryAsync as xtry | xdeferAsync as xdefer | yresAsync as yres |
| @zokugun/xtry/sync | Synchronous-only | xtrySync as xtry | xdeferSync as xdefer | yresSync as yres |
All modules share the same Result, Partial, and stringifyError exports, so you can swap entry points without refactoring types.
Donations
Support this project by becoming a financial contributor.
License
Copyright © 2025-present Baptiste Augrain
Licensed under the MIT license.
