@noctuatech/result
v1.2.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 { Ok, Err, type Result } from "@noctuatech/result";
const a: Result<number, string> = new Ok(42);
const b: Result<number, string> = new Err("boom");Most useful 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); // 5000You can also pass a function to compute a fallback from the error.
const timeoutMs = Result.err("missing config").unwrapOr((err) => {
console.warn(err);
return 5000;
});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());
}API summary
- Constructors:
new Ok(value),new Err(error) - Helpers:
Result.ok(value),Result.err(error) - Transform:
map,andThen,mapErr - Read values:
unwrap,unwrapOr(value or function),expect,expectErr - Wrappers:
Result.wrap,Result.wrapAsync - Retry:
Result.attempt - Collect:
Result.all - Type guard:
Result.isResult
Development
npm run build
npm testLicense
MIT
