惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
G
Google Developers Blog
L
LangChain Blog
腾讯CDC
大猫的无限游戏
大猫的无限游戏
U
Unit 42
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
小众软件
小众软件
The GitHub Blog
The GitHub Blog
博客园_首页
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
I ported Rust's Result and Option types to TypeScript
MadKarma ✭ · 2026-06-26 · via DEV Community

If you've used Rust, you know how nice it is to have Result<T, E> and Option<T> types that make failure and absence explicit instead of something that blows up at runtime. I wanted that in TypeScript, so I built results-ts.

It's not a novel idea. There are a lot of similar libraries out there. But I wanted something that felt close to the real Rust API, worked with async code without being awkward, and didn't cut corners on type safety.


Installation

npm install results-ts
# or
bun add results-ts
# or
pnpm add results-ts
# or
deno add results-ts
# or
yarn add results-ts


Result

Result<T, E> is either Ok(value) or Err(error). You get a concrete type for both sides, which means TypeScript can actually help you when you handle them.

import { Ok, Err } from 'results-ts';

const parseUserId = (id: string) => {
    const parsed = parseInt(id, 10);
    if (isNaN(parsed))
        return Err({ code: 'INVALID_INPUT', message: 'ID must be a valid number' } as const);
    if (parsed <= 0)
        return Err({ code: 'INVALID_ID', message: 'ID must be positive' } as const);
    return Ok(parsed);
};

From there you can chain operations. .map() transforms the Ok value, .andThen() lets you sequence two fallible operations, and .match() handles both branches:

const fetchUser = (id: number) => {
    if (id === 13) return Err({ code: 'NOT_FOUND', message: 'User not found' } as const);
    return Ok({ id, name: 'Alice', role: 'admin' });
};

const message = parseUserId('10')
    .map((id) => id + 3)
    .andThen(fetchUser)
    .match({
        Ok: (user) => `Welcome, ${user.role} ${user.name}!`,
        Err: (error) => {
            if (error.code === 'NOT_FOUND') return `Database Error: ${error.message}`;
            return `Validation Error: ${error.message}`;
        }
    });

Because as const is used on the error objects, TypeScript knows every possible code value and will complain if you miss one in .match().


Option

Option<T> is Some(value) or None(). It's basically T | null | undefined but with methods on it, so you don't have to break out of the chain to check for emptiness.

import { Some, None } from 'results-ts';

const parseNickname = (nickname?: string) => {
    if (!nickname) return None();
    const trimmed = nickname.trim();
    return trimmed.length > 0 ? Some(trimmed) : None();
};

const displayName = parseNickname('  Ada  ')
    .map((name) => name.toUpperCase())
    .match({
        Some: (name) => name,
        None: () => 'ANONYMOUS'
    });

console.log(displayName); // "ADA"


Wrapping code that throws

You can't always rewrite everything. catchUnwind wraps a throwing function so it returns a Result instead:

import { catchUnwind } from 'results-ts';

const safeParse = catchUnwind(
    JSON.parse,
    (thrown) => thrown instanceof Error ? thrown.message : 'parse error'
);

safeParse('{"a":1}'); // Ok({ a: 1 })
safeParse('{bad');    // Err('Unexpected token ...')

The second argument maps the thrown value to an error type of your choice. Leave it out and the error type becomes unknown, since JS lets you throw anything, that's the honest type.

For async functions there's catchUnwindAsync, which catches both sync throws and rejected promises.


Async

AsyncResult<T, E> and AsyncOption<T> are promise wrappers that keep the same chainable API. No need to await in the middle of a chain just to call .map().

import { catchUnwindAsync } from 'results-ts';

const safeFetch = catchUnwindAsync(
    async (url: string) => {
        const res = await fetch(url);
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
    },
    (thrown) => (thrown instanceof Error ? thrown.message : 'request failed')
);

const result = await safeFetch('https://api.example.com');
//    ^? AsyncResult<unknown, string>


On .unwrap() and panics

Methods like .unwrap() and .expect() deliberately "panic" (throw), same as Rust. The idea is they're for cases where you're certain something is Ok, and if it isn't, you want a loud failure rather than a silent wrong value. They're not for normal error handling.

If a non-panic error comes out of the library, that's a bug on the call site (garbage data, type system bypass, etc.), not something to catch.


Performance

Overhead is minimal, full numbers in BENCHMARKS.md.


Browser / no bundler

It's an ES module, so you can import it straight from a CDN:

<script type="module">
    import { Ok } from 'https://unpkg.com/results-ts/dist/index.js';
    console.log(Ok(1).map((x) => x + 1).unwrap()); // 2
</script>


That's about it. If you try it out, feedback is welcome, issues and PRs are open on GitHub.