Skip to content

Latest commit

 

History

105 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

License: MIT NPM Version NodeJS Version Modules Ko-fi Liberapay PayPal

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 Result object with zero dependencies.
  • Strong TypeScript types guide your control flow (fails and tagged errors).
  • Optional failure handlers let you log, meter, or mutate state exactly where the error occurs.

Installation

npm install @zokugun/xtry

Requirements

  • Node.js: v18 or higher
  • Modules: ECMAScript Modules (ESM) or CommonJS (CJS)

Quick 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(): void {
   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 fails first, then use other flags (success, custom issue or value) 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>>;

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 handler before turning that value into err(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) and xtryifyAsync(fn) return new functions that forward arguments to fn and capture thrown/errors as Result objects.
  • xtryifySyncIterable(fn) and xtryifyAsyncIterable(fn) wrap functions that return iterables/async iterables, yielding streams of Result values.

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<I> = { fails: false; success: false; issue: I; value: undefined; error: undefined };
type YResult<T, E, I> = Failure<E> | YSuccess<T> | YFailure<I>;

function yok<T>(value: T): YSuccess<T>;
function yerr<I>(type: I): YFailure<I>;
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();
}
  • xdefer inspects 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/xdeferAsync let you pin the behavior explicitly for bundlers.
  • Calling the returnedfunction with no arguments just runs the deferred work and yields ok().

Unwrap Helpers

The unwrap entry point provides functional helpers for consuming Result values:

import { map, match, unwrap, unwrapOr } from '@zokugun/xtry/unwrap';

const content1 = unwrap(readFile(filePath)); // throw if the result failed
const content2 = unwrapOr(readFile(filePath), 'fallback');
const content3 = map(readFile(filePath), (value) => value.toString());

const content4 = match(readFile(filePath), {
   failure: (error) => `Failed: ${error}`,
   success: (value) => `Value: ${value}`,
});
function map<T, E, U>(result: Result<T, E>, transform: (value: T) => U): Result<U, E>;
function match<T, E, R>(result: Result<T, E>, handlers: { failure: (error: E) => R; success: (value: T) => R }): R;
function unwrap<T, E>(result: Result<T, E>): T;
function unwrapOr<T, E, F>(result: Result<T, E>, fallback: F): F | T;
  • map transforms successful values and preserves failures unchanged.
  • match invokes exactly one handler based on the result state.
  • unwrap returns the successful value, rethrows Error failures, and converts other failures to Error instances.
  • unwrapOr returns the successful value or the supplied fallback.

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 All 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/json JSON helpers
@zokugun/xtry/result Result types, ok and err
@zokugun/xtry/sync Synchronous-only xtrySync as xtry xdeferSync as xdefer yresSync as yres
@zokugun/xtry/unwrap Unwrap helpers

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.

Ko-fi ko-fi.com/daiyam
Liberapay liberapay.com/daiyam/donate
PayPal paypal.me/daiyam99

License

Copyright © 2025-present Baptiste Augrain

Licensed under the MIT license.

About

simple try/catch wrapper returning Result

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages