typescript
43 lines · 8 steps
A Result type for typed error handling
A discriminated union that models success or failure as a value, with combinators to transform it without throwing.
Explained by
highlit
1type Ok<T> = { ok: true; value: T };
2type Err<E> = { ok: false; error: E };
3export type Result<T, E> = Ok<T> | Err<E>;
4
5export const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
6export const err = <E>(error: E): Err<E> => ({ ok: false, error });
7
8export function isOk<T, E>(result: Result<T, E>): result is Ok<T> {
9 return result.ok;
10}
11
12export function map<T, U, E>(
13 result: Result<T, E>,
14 fn: (value: T) => U,
15): Result<U, E> {
16 return result.ok ? ok(fn(result.value)) : result;
17}
18
19export function andThen<T, U, E>(
20 result: Result<T, E>,
21 fn: (value: T) => Result<U, E>,
22): Result<U, E> {
23 return result.ok ? fn(result.value) : result;
24}
25
26export function mapErr<T, E, F>(
27 result: Result<T, E>,
28 fn: (error: E) => F,
29): Result<T, F> {
30 return result.ok ? result : err(fn(result.error));
31}
32
33export function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T {
34 return result.ok ? result.value : fallback;
35}
36
37export function fromThrowable<T>(fn: () => T): Result<T, Error> {
38 try {
39 return ok(fn());
40 } catch (e) {
41 return err(e instanceof Error ? e : new Error(String(e)));
42 }
43}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Modeling failure as a value instead of an exception makes error paths explicit and type-checked.
- 2A shared boolean tag lets TypeScript narrow a union to exactly one variant after a truthiness check.
- 3Small combinators like map and andThen let you chain fallible steps while short-circuiting on the first error.
Related explainers
javascript
const express = require('express'); const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware'); const router = express.Router();
Building an API gateway proxy in Express
reverse-proxy
middleware
api-gateway
Intermediate
6 steps
rust
use axum::{ extract::{FromRequestParts, Query}, http::{request::Parts, StatusCode}, };
Building a custom Axum extractor for query filters
extractors
query-parsing
enums
Intermediate
8 steps
typescript
import { Injectable, signal, computed, effect, inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; export type Theme = 'light' | 'dark';
A signal-based theme service in Angular
signals
reactivity
dependency-injection
Intermediate
7 steps
python
import asyncio from dataclasses import dataclass import aiohttp
Bounded-concurrency HTTP fetching with asyncio
async
concurrency
semaphore
Intermediate
8 steps
typescript
import { Injectable } from '@angular/core'; import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map, distinctUntilChanged, scan } from 'rxjs/operators';
Tracking upload progress in Angular
rxjs
http-events
state-reduction
Intermediate
8 steps
go
package api import ( "errors"
Turning Gin validation errors into JSON
validation
error-handling
http-handlers
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-result-type-for-typed-error-handling-explained-typescript-0b6c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.