Yieldless
Reference

yieldless/error

Tuple-based error handling primitives.

yieldless/error is the smallest useful piece of the library. It gives you a single tuple shape and a few helpers for converting thrown code into that shape.

Exports

  • type SafeResult<T, E = Error> = [E, null] | [null, T]
  • ok(value): SafeResult<T, never>
  • err(error): SafeResult<never, E>
  • safeTry(promise): Promise<SafeResult<T>>
  • safeTrySync(fn): SafeResult<T>
  • match(result, { ok, err }): Return
  • unwrap(result): T

Typical use

import { err, match, ok, safeTry, safeTrySync, unwrap } from "yieldless/error";

const [readError, body] = await safeTry(readFile("package.json", "utf8"));
if (readError) {
  return err(readError);
}

const parsed = safeTrySync(() => JSON.parse(body));
const value = unwrap(parsed);
const state = match(ok(value), {
  ok: (data) => ({ kind: "ready", data }),
  err: (error) => ({ kind: "error", message: String(error) }),
});

When to use it

  • Wrapping filesystem, HTTP, database, or subprocess calls
  • Converting parse and validation failures into explicit branches
  • Leaving framework boundaries as tuples until the last possible moment, then folding them with match()

Rules of thumb

  • Prefer safeTry() at the boundary, not around every individual expression.
  • Use ok() and err() when returning tuples so the intent reads clearly in application code.
  • Keep the tuple local. Once you have the success value, use the value or fold it with match().
  • Use unwrap() only where a thrown exception is genuinely required.

Good

Wrap the operation that can fail, check the error slot once, then continue with normal values.

const [readError, text] = await safeTry(readConfigFile());
if (readError) {
  return err(readError);
}

const [parseError, config] = safeTrySync(() => JSON.parse(text));
if (parseError) {
  return err(parseError);
}

return ok(config);

Use match() when crossing into UI state or framework output.

const view = match(result, {
  ok: (user) => ({ status: "ready" as const, user }),
  err: (error) => ({ status: "failed" as const, message: String(error) }),
});

Avoid

Do not ignore the error slot just because a type checker lets you.

const result = await safeTry(readConfigFile());
const config = JSON.parse(result[1] as string);

Do not use unwrap() as a replacement for tuple handling in service code.

const user = unwrap(await safeTry(loadUser(id)));
return ok(user);

Caveat

SafeResult uses null sentinels. If your success value is literally null, the runtime tuple is still correct, but the type system cannot fully discriminate that case.

On this page