Stop Throwing Exceptions. Use Option and Result Instead.
Source: Dev.to
The Problem with JavaScript Error Handling
function getUser(id: number): User | null {
// ...
}
The caller must remember to null‑check. The type system nudges them, but nothing prevents this:
const user = getUser(42);
console.log(user.name); // TypeError at runtime if user is null
A worse pattern is hiding possible failures in an async function:
async function fetchConfig(): Promise {
// can throw network error, parse error, validation error...
// none of these appear in the type signature
}
The errors are invisible, so the caller doesn’t know what to handle.
Introducing Option and Result in TypeScript
Rust’s Option and Result make the possibility of absence or failure explicit in the type signature. The same idea is brought to TypeScript by @rslike/std.
npm i @rslike/std
Working with Option
import { Some, None, Option, match } from "@rslike/std";
function findUser(id: number): Option {
const user = db.find(u => u.id === id);
return user ? Some(user) : None();
}
Safe Extraction
const opt = findUser(42);
// Fallback value
const user = opt.unwrapOr(guestUser);
Pattern Matching
const greeting = match(
opt,
(user) => `Hello, ${user.name}!`,
() => "Hello, guest!"
);
Transformations
const displayName = findUser(42)
.map(u => `${u.firstName} ${u.lastName}`)
.unwrapOr("Unknown User");
Chaining Options
const avatar = findUser(42)
.flatMap(u => findAvatar(u.avatarId))
.unwrapOr(defaultAvatar);
Basic API
const opt = Some("hello");
opt.isSome(); // true
opt.isNone(); // false
opt.unwrap(); // "hello"
const empty = None();
empty.isNone(); // true
empty.unwrapOr("fallback"); // "fallback"
empty.unwrap(); // throws UndefinedBehaviorError — intentional!
Working with Result
import { Ok, Err, Result, match } from "@rslike/std";
function divide(a: number, b: number): Result {
return new Result((ok, err) => {
if (b === 0) {
err("Division by zero");
} else {
ok(a / b);
}
});
}
Inspecting the Result
const r = divide(10, 2);
r.isOk(); // true
r.unwrap(); // 5
const bad = divide(10, 0);
bad.isErr(); // true
bad.unwrapErr(); // "Division by zero"
bad.unwrapOr(0); // 0
Parsing JSON with Result
function parseJSON(raw: string): Result {
return new Result((ok, err) => {
try {
ok(JSON.parse(raw));
} catch (e) {
err(e as SyntaxError);
}
});
}
const config = parseJSON(rawInput)
.map(data => validate(data))
.mapErr(e => `Invalid config: ${e.message}`)
.unwrapOr(defaults);
Matching on Result
const message = match(
parseJSON(rawInput),
(data) => `Loaded: ${JSON.stringify(data)}`,
(err) => `Error: ${err.message}`
);
The match Utility
match works with booleans, Option, and Result, providing exhaustive two‑branch dispatch.
import { match } from "@rslike/std";
// Boolean
match(isAdmin, (t) => "admin panel", (f) => "dashboard");
// Option
match(someOption, (value) => `Got: ${value}`, () => "nothing");
// Result
match(someResult, (n) => n * 2, (e) => -1);
TypeScript infers the callback parameter types from the input, preventing accidental misuse of the error handler as the success handler.
Global Imports (Optional)
// entry.ts — once
import "@rslike/std/globals";
// Anywhere else in your app — no imports needed
const x = Some(42);
const r = Ok("success");
const n = None();
const e = Err(new Error("oops"));
Benefits for Code Review
When Option and Result appear in function signatures, discussions shift from “Did we forget to handle a null/exception?” to “What is the intended contract?”
| Before | After |
|---|---|
function getSession(token: string): Session | null | function getSession(token: string): Option |
async function createOrder(cart: Cart): Promise | async function createOrder(cart: Cart): Promise> |
Installation
npm i @rslike/std