Documentation Index
Fetch the complete documentation index at: https://mintlify.com/supermacro/neverthrow/llms.txt
Use this file to discover all available pages before exploring further.
TypeScript Best Practices
Get the most out of NeverThrow with these TypeScript tips and patterns for better type safety and developer experience.
Type Inference Best Practices
Let TypeScript Infer When Possible
In most cases, TypeScript can infer the types of your Results automatically:
import { ok, err, Result } from 'neverthrow'
// Type is inferred as Result<number, never>
const success = ok(42)
// Type is inferred as Result<never, string>
const failure = err('Something went wrong')
// Explicit typing when needed
const result: Result<number, string> = someCondition
? ok(42)
: err('error')
Avoid Explicit Type Arguments Unless Required
Most methods like map, andThen, and match infer types correctly:
// Good: Types are inferred
const result = ok(5)
.map(x => x * 2)
.andThen(x => ok(x.toString()))
// Unnecessary: Explicit types not needed
const result = ok<number, never>(5)
.map<number>(x => x * 2)
.andThen<string, never>(x => ok(x.toString()))
Exception: Explicit type arguments are needed when migrating to v8.0.0 for orElse if you were using explicit types.
Working with Union Types
Distinct Error Types with andThen
Starting in v4.1.0, andThen allows you to return distinct error types that are merged into a union:
type ParseError = { type: 'parse'; message: string }
type ValidationError = { type: 'validation'; field: string }
type DatabaseError = { type: 'database'; code: number }
function parseInput(raw: string): Result<User, ParseError> {
// ...
}
function validateUser(user: User): Result<User, ValidationError> {
// ...
}
function saveUser(user: User): ResultAsync<User, DatabaseError> {
// ...
}
// Error type is automatically: ParseError | ValidationError | DatabaseError
const result = parseInput(input)
.andThen(validateUser)
.asyncAndThen(saveUser)
Discriminated Unions for Error Handling
Use discriminated unions with error types for exhaustive error handling:
type AppError =
| { type: 'network'; statusCode: number }
| { type: 'validation'; fields: string[] }
| { type: 'auth'; reason: string }
function handleError(error: AppError): string {
switch (error.type) {
case 'network':
return `Network error: ${error.statusCode}`
case 'validation':
return `Invalid fields: ${error.fields.join(', ')}`
case 'auth':
return `Auth failed: ${error.reason}`
// TypeScript ensures all cases are handled
}
}
const result: Result<Data, AppError> = fetchData()
result.mapErr(handleError)
Advanced Pattern Matching
Type Narrowing with String Literals
As of v7.1.0, err() infers strings narrowly for easier error tagging:
// Error type is inferred as 'NotFound' (not string)
const notFound = err('NotFound')
// Useful for pattern matching
const result = fetchUser(id)
if (result.isErr()) {
switch (result.error) {
case 'NotFound':
return 404
case 'Unauthorized':
return 401
default:
return 500
}
}
Generic Result Handlers
Create reusable type-safe handlers:
type Handler<T, E, R> = {
onOk: (value: T) => R
onErr: (error: E) => R
}
function handle<T, E, R>(
result: Result<T, E>,
handler: Handler<T, E, R>
): R {
return result.match(handler.onOk, handler.onErr)
}
// Usage
const httpHandler: Handler<User, AppError, Response> = {
onOk: (user) => res.json(user),
onErr: (error) => res.status(getStatusCode(error)).json({ error })
}
handle(getUserResult, httpHandler)
Async Patterns
Combining Sync and Async Results
Use asyncAndThen to transition from Result to ResultAsync:
import { Result, ResultAsync } from 'neverthrow'
function validateInput(data: unknown): Result<Input, ValidationError> {
// Synchronous validation
}
function saveToDb(input: Input): ResultAsync<Saved, DbError> {
// Async database operation
}
// Seamlessly chain sync and async
const result: ResultAsync<Saved, ValidationError | DbError> =
validateInput(data).asyncAndThen(saveToDb)
Working with Promise
Convert promises to ResultAsync safely:
import { ResultAsync } from 'neverthrow'
// From throwing promise
const result = ResultAsync.fromPromise(
fetch('/api/users'),
(error) => ({ type: 'network' as const, error })
)
// From safe promise (won't throw)
const safeResult = ResultAsync.fromSafePromise(
Promise.resolve({ id: 1, name: 'Alice' })
)
Using safeTry with Async Generators
safeTry supports async generator functions for cleaner async error handling:
import { safeTry, ResultAsync } from 'neverthrow'
declare function fetchUser(id: string): ResultAsync<User, FetchError>
declare function fetchPosts(userId: string): ResultAsync<Post[], FetchError>
function getUserWithPosts(id: string): ResultAsync<UserWithPosts, FetchError> {
return safeTry<UserWithPosts, FetchError>(async function*() {
// Automatically unwraps or returns early on error
const user = yield* fetchUser(id)
const posts = yield* fetchPosts(user.id)
return ok({ user, posts })
})
}
Handling Multiple Results
Homogeneous Lists
Combine arrays of Results with the same type:
import { Result, ok, err } from 'neverthrow'
const results: Result<number, string>[] = [
ok(1),
ok(2),
ok(3)
]
// Type: Result<number[], string>
const combined = Result.combine(results)
combined.map(numbers => {
// numbers is number[]
console.log(numbers.reduce((a, b) => a + b, 0))
})
Heterogeneous Tuples
Combine Results with different types using tuples:
import { Result, ok } from 'neverthrow'
const tuple = <T extends any[]>(...args: T): T => args
const results = tuple(
ok('Alice'),
ok(25),
ok(true)
)
// Type: Result<[string, number, boolean], never>
const combined = Result.combine(results)
combined.map(([name, age, active]) => {
// Full type safety for each element
console.log(`${name} is ${age} years old and ${active ? 'active' : 'inactive'}`)
})
Collecting All Errors
Use combineWithAllErrors when you need all error values:
import { Result, ok, err } from 'neverthrow'
type ValidationError = { field: string; message: string }
const results: Result<string, ValidationError>[] = [
ok('valid'),
err({ field: 'email', message: 'Invalid email' }),
ok('also valid'),
err({ field: 'password', message: 'Too short' })
]
// Type: Result<string[], ValidationError[]>
const combined = Result.combineWithAllErrors(results)
combined.mapErr(errors => {
// errors contains only the failed validation errors
errors.forEach(e => console.error(`${e.field}: ${e.message}`))
})
Type Guards and Narrowing
Using Type Predicates
Combine Results with custom type guards:
function isUser(value: unknown): value is User {
return typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value
}
function parseUser(data: unknown): Result<User, string> {
if (isUser(data)) {
return ok(data)
}
return err('Invalid user data')
}
Exhaustive Matching
TypeScript ensures exhaustive handling in match callbacks:
type Status = 'pending' | 'success' | 'failed'
function getResult(status: Status): Result<string, string> {
// TypeScript ensures all cases are handled
switch (status) {
case 'pending':
return err('Still processing')
case 'success':
return ok('Complete')
case 'failed':
return err('Failed')
// If you add a new status, TypeScript will error here
}
}
Avoid Unnecessary Unwrapping
Stay in the Result context as long as possible:
// Avoid: Early unwrapping
const value = result.unwrapOr(0)
const doubled = value * 2
const tripled = doubled * 3
// Better: Chain operations
const final = result
.map(x => x * 2)
.map(x => x * 3)
.unwrapOr(0)
Reuse Error Mappers
Create reusable error transformation functions:
const toAppError = (error: unknown): AppError => {
if (error instanceof Error) {
return { type: 'system', message: error.message }
}
return { type: 'unknown', message: 'An error occurred' }
}
// Reuse across your codebase
const result1 = Result.fromThrowable(mayThrow1, toAppError)
const result2 = Result.fromThrowable(mayThrow2, toAppError)
Common Patterns
Railway-Oriented Programming
Chain operations on the “happy path”:
function processOrder(orderId: string): ResultAsync<Receipt, OrderError> {
return validateOrder(orderId)
.andThen(checkInventory)
.asyncAndThen(chargePayment)
.andThen(updateInventory)
.asyncAndThen(sendConfirmation)
.map(createReceipt)
}
// Errors automatically short-circuit and propagate
Side Effects with Type Safety
Use andTee and orTee for side effects:
const result = processUser(data)
.andTee(user => logger.info('User processed', user))
.andThen(saveToDatabase)
.orTee(error => logger.error('Failed', error))
.map(notifySuccess)
Further Reading