Error handling
This library distinguishes two kinds of failure. Understanding both keeps your code predictable and type-safe.
Panics
Some methods intentionally panic, mirroring Rust. They throw an internal PanicError synchronously - or, on the async wrappers, reject the returned Promise with one:
| Method | Panics when |
|---|---|
Result.unwrap()Option.unwrap()AsyncResult.unwrap()AsyncOption.unwrap() | the outcome is Err or None |
Result.expect()Option.expect()AsyncResult.expect()AsyncOption.expect() | the outcome is Err or None |
Result.unwrapErr()AsyncResult.unwrapErr() | the value is Ok |
Result.expectErr()AsyncResult.expectErr() | the value is Ok |
Use a panic only when reaching an Err or None would mean a bug - for example, after earlier logic has guaranteed success or the presence of a value. When failure or missing data is an expected outcome, handle it explicitly with a non-panicking combinator:
Result.unwrapOr(fallback)/Option.unwrapOr(fallback)- a default value;Result.unwrapOrElse((err) => fallback)/Option.unwrapOrElse(() => fallback)- a lazily-computed default;Result.match({ Ok, Err })/Option.match({ Some, None })- explicit branching.
import { Ok, Err } from 'results-ts';
// recoverable - no panic:
const n = Ok(1).unwrapOr(0); // 1
const m = Err('oops').unwrapOr(0); // 0
// assertion - panics if you are wrong:
const k = Ok(1).unwrap(); // 1
const bad = Err('oops').unwrap(); // throws PanicErrorMisuse errors
Almost every method validates its arguments at runtime. If untyped JavaScript or an unsafe type assertion passes a value that violates a method's contract, the method throws a JavaScript error. These errors signal a bug at the call site, not a normal control-flow path.
Error classes are not exported
Internal error classes like PanicError and InvalidArgumentError are intentionally not exported. Thrown errors report broken assumptions or invalid calls; Result and Option are the public APIs for expected failures and absent values.
catchUnwind
Existing or third-party code often throws exceptions or rejects promises. catchUnwind and catchUnwindAsync provide a gentle boundary between that code and a Result pipeline. New code whose failures are expected should usually return Result directly.
catchUnwind wraps a synchronous function and turns a throw into an Err. Its optional onThrow handler can normalize JavaScript's unknown thrown value into a typed error.
import { catchUnwind } from 'results-ts';
const parseJson = (text: string): unknown => JSON.parse(text);
const safeParse = catchUnwind(parseJson, (thrown) =>
thrown instanceof Error ? thrown.message : 'parse error'
);
safeParse('{"a":1}'); // Ok({ a: 1 })
safeParse('{bad'); // Err('Unexpected token ...')Without an onThrow handler, the caught error type remains unknown, because JavaScript allows throwing any value:
import { catchUnwind } from 'results-ts';
const unsafe = catchUnwind(() => {
throw 'literal string';
});
const result = unsafe();
// ^? Result<never, unknown>catchUnwindAsync captures both synchronous throws and rejected promises and returns an AsyncResult:
import { catchUnwindAsync } from 'results-ts';
const readJson = async (response: Response): Promise<unknown> =>
response.json();
const safeFetch = catchUnwindAsync(
async (url: string) => {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return readJson(response);
},
(thrown) => (thrown instanceof Error ? thrown.message : 'request failed')
);
const result = await safeFetch('https://api.example.com');
// ^? Result<unknown, string>NOTE
The onThrow handler receives the thrown value and the original call arguments: (thrown, ...args) => E.
How this differs from Rust
Rust uses the match keyword syntax; this library provides Result.match() and Option.match() methods to achieve the same branching style in TypeScript. The panic and unwrap semantics, plus the Ok / Err / Some / None naming, follow the Rust originals closely. See the results-ts API reference for this library's behavior and the official Rust docs for Rust's standard library.
Next steps
- Async guide - use the same pipeline style with asynchronous work.
- API reference - complete signatures and method documentation.