@noctuatech/result
v1.3.0
Published
A small Rust-style `Result` for TypeScript/JavaScript.
Readme
@noctuatech/result
A small Rust-style Result for TypeScript/JavaScript.
Use it when you want predictable error handling without relying on thrown exceptions in your app logic.
Install
npm install @noctuatech/resultQuick start
import { Result } from "@noctuatech/result";
function parsePort(input: string) {
const value = Number(input);
return Number.isInteger(value) && value > 0 ? Result.ok(value) : Result.err("Invalid port");
}
const port = parsePort("3000")
.map((n) => n + 1)
.unwrapOr(8080);
console.log(port); // 3001Core idea
A result is always one of these two shapes:
Ok<T>: success valueErr<E>: error value
import { Result } from "@noctuatech/result";
const a: Result<number, string> = Result.ok(42);
const b: Result<number, string> = Result.err("boom");Operations
map
Transform success values only.
const r = Result.ok(10).map((n) => n * 2); // Ok(20)andThen
Chain operations that each return a result.
function toNumber(input: string) {
const n = Number(input);
return Number.isFinite(n) ? Result.ok(n) : Result.err("Not a number");
}
function positive(n: number) {
return n > 0 ? Result.ok(n) : Result.err("Must be positive");
}
const res = toNumber("12").andThen(positive); // Ok(12)mapErr
Transform error values only.
const res = Result.err("timeout").mapErr((e) => `Network error: ${e}`);
// Err("Network error: timeout")unwrapOr
Get a safe fallback value.
const timeoutMs = Result.err("missing config").unwrapOr(5000); // 5000unwrapOrElse
Compute a fallback from the error using a closure.
const timeoutMs = Result.err("missing config").unwrapOrElse((err) => {
console.warn(err);
return 5000;
});orElse
Chain operations on error results. Returns the original Ok value, or calls the mapper with the error value and returns its result if it is an Err.
const res = Result.err("timeout").orElse((err) => Result.ok(3000)); // Ok(3000)mapOr
Returns the default value if Err, or applies the mapper function to the contained Ok value.
const len = Result.ok("hello").mapOr(0, (s) => s.length); // 5
const lenFallback = Result.err("error").mapOr(0, (s) => s.length); // 0mapOrElse
Maps a Result<T, E> to U by applying a fallback function to a contained Err, or a mapper function to a contained Ok value.
const len = Result.ok("hello").mapOrElse(
(err) => 0,
(s) => s.length,
); // 5
const lenFallback = Result.err("error").mapOrElse(
(err) => err.length,
(s) => s.length,
); // 5Async Operations (mapAsync and andThenAsync)
Transform and chain async operations directly on Results:
// mapAsync: map the success value asynchronously
const mapped = await Result.ok(5).mapAsync(async (x) => x * 2); // Ok(10)
// andThenAsync: chain an async operation returning another Result
const chained = await Result.ok(5).andThenAsync(async (x) => Result.ok(x * 2)); // Ok(10)Wrapping throwing code
Result.wrap for sync code
const parsed = Result.wrap(() => JSON.parse('{"ok":true}'));
if (parsed.ok) {
console.log(parsed.val.ok);
} else {
console.error(parsed.val);
}Result.wrapAsync for async code
const userResult = await Result.wrapAsync(async () => {
const response = await fetch("https://example.com/user");
if (!response.ok) throw new Error("Request failed");
return response.json();
});Retry helper
Use Result.attempt when your async function returns a Result and you want retry behavior.
let tries = 0;
const result = await Result.attempt(
async () => {
tries++;
return tries < 3 ? Result.err("temporary error") : Result.ok("done");
},
{
attempts: 5,
timeout: 200,
backoff: 0.5,
},
);
console.log(result.toString()); // Ok(done)Notes:
attemptsdefault is3timeoutis the initial wait between retries in millisecondsbackoffincreases wait time after each retry- final failed result is marked with
attempted = true
Collecting multiple results
Use Result.all to combine an array (or tuple) of Results into a single Result. It returns Ok with all values if every result succeeded, or the first Err it encounters.
const results = Result.all([
Result.wrap(() => JSON.parse('{"port":3000}')),
Result.wrap(() => JSON.parse('{"host":"localhost"}')),
]);
if (results.ok) {
const [portConfig, hostConfig] = results.val;
}Tuple types are fully preserved, so each element's type is inferred independently.
Runtime checks
const maybe = Math.random() > 0.5 ? Result.ok(1) : "not a result";
if (Result.isResult(maybe)) {
console.log(maybe.toString());
}Stack trace configuration
By default, Err objects capture the stack trace from where they were constructed. If you are using results in a performance-critical hot path, you can disable stack trace capturing globally:
import { Result } from "@noctuatech/result";
Result.captureStackTrace = false;API summary
- Constructors:
new Ok(value),new Err(error) - Helpers:
Result.ok(value),Result.err(error) - Transform:
map,andThen,mapErr,orElse,mapOr,mapOrElse,mapAsync,andThenAsync - Read values:
unwrap,unwrapOr,unwrapOrElse,expect,expectErr - Clone:
clone - Wrappers:
Result.wrap,Result.wrapAsync - Retry:
Result.attempt - Collect:
Result.all - Type guard:
Result.isResult - Configuration:
Result.captureStackTrace
Development
npm run build
npm testLicense
MIT
