Tools: Stop Throwing Exceptions. Use Option and Result Instead.

Tools: Stop Throwing Exceptions. Use Option and Result Instead.

Source: Dev.to

There's a better model ## Option — make absence impossible to ignore ## Transform without unwrapping ## Quick checks ## Result — make errors part of the contract ## Wrapping existing code that throws ## Pattern matching ## match — exhaustive two-branch dispatch ## Globals — skip the imports ## The real benefit ## Install Let's talk about what's wrong with JavaScript error handling. Here's a function: The caller has to remember to null-check. The type system nudges them, but there's nothing stopping this: Or this pattern, which is even worse: The errors are invisible. The caller doesn't know what to handle. Rust has Option<T> for values that might not exist, and Result<T, E> for operations that can fail. Both are explicit in the type signature. Both force the caller to handle every case. @rslike/std brings this to TypeScript. Now the caller must handle both states. They can't accidentally treat None as a value: The error type is in the signature. Callers know what to expect: match works with Option, Result, and boolean: TypeScript infers the callback parameter types from the input — you can't accidentally use the error handler as the success handler. When Option and Result are in your function signatures, code review becomes a conversation about intent rather than a hunt for unhandled edge cases. Source: github.com/vitalics/rslike Have you used Option/Result patterns in TypeScript before? What library are you using? Let me know below. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: function getUser(id: number): User | null { // ... } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: function getUser(id: number): User | null { // ... } CODE_BLOCK: function getUser(id: number): User | null { // ... } CODE_BLOCK: const user = getUser(42); console.log(user.name); // TypeError at runtime if user is null Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const user = getUser(42); console.log(user.name); // TypeError at runtime if user is null CODE_BLOCK: const user = getUser(42); console.log(user.name); // TypeError at runtime if user is null COMMAND_BLOCK: async function fetchConfig(): Promise<Config> { // can throw network error, parse error, validation error... // none of these appear in the type signature } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: async function fetchConfig(): Promise<Config> { // can throw network error, parse error, validation error... // none of these appear in the type signature } COMMAND_BLOCK: async function fetchConfig(): Promise<Config> { // can throw network error, parse error, validation error... // none of these appear in the type signature } COMMAND_BLOCK: npm i @rslike/std Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm i @rslike/std COMMAND_BLOCK: npm i @rslike/std COMMAND_BLOCK: import { Some, None, Option, match } from "@rslike/std"; function findUser(id: number): Option<User> { const user = db.find(u => u.id === id); return user ? Some(user) : None(); } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { Some, None, Option, match } from "@rslike/std"; function findUser(id: number): Option<User> { const user = db.find(u => u.id === id); return user ? Some(user) : None(); } COMMAND_BLOCK: import { Some, None, Option, match } from "@rslike/std"; function findUser(id: number): Option<User> { const user = db.find(u => u.id === id); return user ? Some(user) : None(); } COMMAND_BLOCK: const opt = findUser(42); // Safe extraction with fallback const user = opt.unwrapOr(guestUser); // Pattern matching — handles both branches exhaustively const greeting = match( opt, (user) => `Hello, ${user.name}!`, () => "Hello, guest!" ); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const opt = findUser(42); // Safe extraction with fallback const user = opt.unwrapOr(guestUser); // Pattern matching — handles both branches exhaustively const greeting = match( opt, (user) => `Hello, ${user.name}!`, () => "Hello, guest!" ); COMMAND_BLOCK: const opt = findUser(42); // Safe extraction with fallback const user = opt.unwrapOr(guestUser); // Pattern matching — handles both branches exhaustively const greeting = match( opt, (user) => `Hello, ${user.name}!`, () => "Hello, guest!" ); COMMAND_BLOCK: const displayName = findUser(42) .map(u => `${u.firstName} ${u.lastName}`) .unwrapOr("Unknown User"); // Chain operations that also return Option const avatar = findUser(42) .flatMap(u => findAvatar(u.avatarId)) .unwrapOr(defaultAvatar); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const displayName = findUser(42) .map(u => `${u.firstName} ${u.lastName}`) .unwrapOr("Unknown User"); // Chain operations that also return Option const avatar = findUser(42) .flatMap(u => findAvatar(u.avatarId)) .unwrapOr(defaultAvatar); COMMAND_BLOCK: const displayName = findUser(42) .map(u => `${u.firstName} ${u.lastName}`) .unwrapOr("Unknown User"); // Chain operations that also return Option const avatar = findUser(42) .flatMap(u => findAvatar(u.avatarId)) .unwrapOr(defaultAvatar); CODE_BLOCK: 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! Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 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! CODE_BLOCK: 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! COMMAND_BLOCK: import { Ok, Err, Result, match } from "@rslike/std"; function divide(a: number, b: number): Result<number, string> { return new Result((ok, err) => { if (b === 0) { err("Division by zero"); } else { ok(a / b); } }); } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { Ok, Err, Result, match } from "@rslike/std"; function divide(a: number, b: number): Result<number, string> { return new Result((ok, err) => { if (b === 0) { err("Division by zero"); } else { ok(a / b); } }); } COMMAND_BLOCK: import { Ok, Err, Result, match } from "@rslike/std"; function divide(a: number, b: number): Result<number, string> { return new Result((ok, err) => { if (b === 0) { err("Division by zero"); } else { ok(a / b); } }); } CODE_BLOCK: 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 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: 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 CODE_BLOCK: 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 COMMAND_BLOCK: function parseJSON(raw: string): Result<unknown, SyntaxError> { 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); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: function parseJSON(raw: string): Result<unknown, SyntaxError> { 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); COMMAND_BLOCK: function parseJSON(raw: string): Result<unknown, SyntaxError> { 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); COMMAND_BLOCK: const message = match( parseJSON(rawInput), (data) => `Loaded: ${JSON.stringify(data)}`, (err) => `Error: ${err.message}` ); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const message = match( parseJSON(rawInput), (data) => `Loaded: ${JSON.stringify(data)}`, (err) => `Error: ${err.message}` ); COMMAND_BLOCK: const message = match( parseJSON(rawInput), (data) => `Loaded: ${JSON.stringify(data)}`, (err) => `Error: ${err.message}` ); COMMAND_BLOCK: import { match } from "@rslike/std"; // boolean match(isAdmin, (t) => "admin panel", (f) => "dashboard"); // Option<string> match(someOption, (value) => `Got: ${value}`, () => "nothing"); // Result<number, Error> match(someResult, (n) => n * 2, (e) => -1); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import { match } from "@rslike/std"; // boolean match(isAdmin, (t) => "admin panel", (f) => "dashboard"); // Option<string> match(someOption, (value) => `Got: ${value}`, () => "nothing"); // Result<number, Error> match(someResult, (n) => n * 2, (e) => -1); COMMAND_BLOCK: import { match } from "@rslike/std"; // boolean match(isAdmin, (t) => "admin panel", (f) => "dashboard"); // Option<string> match(someOption, (value) => `Got: ${value}`, () => "nothing"); // Result<number, Error> match(someResult, (n) => n * 2, (e) => -1); CODE_BLOCK: // 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")); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // 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")); CODE_BLOCK: // 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")); CODE_BLOCK: // Before: what does null mean here? forgotten value? intentional absence? function getSession(token: string): Session | null // After: clear contract — either a session or nothing function getSession(token: string): Option<Session> // Before: can this throw? which errors? async function createOrder(cart: Cart): Promise<Order> // After: explicit failure type in the signature async function createOrder(cart: Cart): Promise<Result<Order, OrderError>> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Before: what does null mean here? forgotten value? intentional absence? function getSession(token: string): Session | null // After: clear contract — either a session or nothing function getSession(token: string): Option<Session> // Before: can this throw? which errors? async function createOrder(cart: Cart): Promise<Order> // After: explicit failure type in the signature async function createOrder(cart: Cart): Promise<Result<Order, OrderError>> CODE_BLOCK: // Before: what does null mean here? forgotten value? intentional absence? function getSession(token: string): Session | null // After: clear contract — either a session or nothing function getSession(token: string): Option<Session> // Before: can this throw? which errors? async function createOrder(cart: Cart): Promise<Order> // After: explicit failure type in the signature async function createOrder(cart: Cart): Promise<Result<Order, OrderError>> COMMAND_BLOCK: npm i @rslike/std Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm i @rslike/std COMMAND_BLOCK: npm i @rslike/std