Overview
Given 2 functions (one for the Ok variant and one for the Err variant), execute the function that matches the Result variant. Both functions must return the same type, and the Result is unwrapped to that type.
Signature
class Result<T, E> {
match<A, B = A>(
ok: (t: T) => A,
err: (e: E) => B
): A | B
}
Parameters
A function to execute if the Result is Ok. Receives the success value.
A function to execute if the Result is Err. Receives the error value.
Returns
Returns A | B - the result of whichever function was executed. Unlike map and mapErr, this unwraps the Result into the return type of the callbacks.
Examples
Basic Usage
import { ok, err } from 'neverthrow'
const okResult = ok(12)
const matched = okResult.match(
(value) => `Success: ${value}`,
(error) => `Error: ${error}`
)
// matched is 'Success: 12'
const errResult = err('failed')
const matchedErr = errResult.match(
(value) => `Success: ${value}`,
(error) => `Error: ${error}`
)
// matchedErr is 'Error: failed'
Side Effects Only
computationThatMightFail()
.match(
(result) => console.log('Success:', result),
(error) => console.error('Error:', error)
)
// Both callbacks return void
Returning Values
const attempt = computationThatMightFail()
.map((str) => str.toUpperCase())
.mapErr((err) => `Error: ${err}`)
// attempt is Result<string, string>
const answer = computationThatMightFail()
.match(
(str) => str.toUpperCase(),
(err) => `Error: ${err}`
)
// answer is string (unwrapped)
HTTP Response Handling
type User = { id: string; name: string }
type ApiError = { status: number; message: string }
function getUser(id: string): Result<User, ApiError> {
// ...
}
const response = getUser('123').match(
(user) => ({
status: 200,
body: { success: true, data: user }
}),
(error) => ({
status: error.status,
body: { success: false, error: error.message }
})
)
// response is always the same shape
Converting to Different Types
type DatabaseResult = Result<User[], DatabaseError>
function formatForApi(result: DatabaseResult): ApiResponse {
return result.match(
(users) => ({
success: true,
data: users,
count: users.length
}),
(error) => ({
success: false,
error: error.message,
code: error.code
})
)
}
Forced Error Handling
// Unlike map/mapErr, match forces you to handle both cases
const value = someComputation().match(
(ok) => ok,
(err) => {
// You MUST handle the error case
// Can't forget like you can with just .map()
return getDefaultValue()
}
)
Equivalent to map + unwrapOr
// These are equivalent when you don't use the error value:
const result1 = computation().match(
(str) => str.toUpperCase(),
() => 'DEFAULT'
)
const result2 = computation()
.map((str) => str.toUpperCase())
.unwrapOr('DEFAULT')
// Both have the same result
type FormData = { email: string; password: string }
type ValidationError = { field: string; message: string }
function validateForm(
data: FormData
): Result<FormData, ValidationError> {
// ... validation logic
}
const formResult = validateForm(formData).match(
(validData) => {
// Submit the form
submitToApi(validData)
return { success: true, message: 'Form submitted!' }
},
(error) => {
// Show error to user
showFieldError(error.field, error.message)
return { success: false, message: error.message }
}
)
Pattern Matching Different Error Types
type AppError =
| { type: 'network'; code: number }
| { type: 'validation'; field: string }
| { type: 'auth'; reason: string }
function handleResult(result: Result<Data, AppError>): string {
return result.match(
(data) => `Loaded ${data.items.length} items`,
(error) => {
switch (error.type) {
case 'network':
return `Network error: ${error.code}`
case 'validation':
return `Invalid ${error.field}`
case 'auth':
return `Authentication failed: ${error.reason}`
}
}
)
}
Implementation Details
From the source code (result.ts:393-395):
match<A, B = A>(ok: (t: T) => A, _err: (e: E) => B): A | B {
return ok(this.value)
}
For Err (result.ts:492-494):
match<A, B = A>(_ok: (t: T) => A, err: (e: E) => B): A | B {
return err(this.error)
}
Key Differences from map/mapErr
- Unwraps the Result: Returns the callback’s return value directly, not wrapped in a Result
- Forces both cases: You must handle both Ok and Err paths
- Same return type: Both callbacks must return compatible types (A | B)
- Cannot be chained: Since it unwraps, you can’t chain more Result methods after match
Notes
match is like chaining map and mapErr, but requires both functions to have the same return type
- Both callbacks must be provided - you cannot skip error handling
- The Result is consumed and unwrapped - you cannot chain more Result methods after
- Recommended by
eslint-plugin-neverthrow as a safe way to consume Results
- For side effects only, both callbacks can return
void