Overview
ResultAsync<T, E> is a class that wraps a Promise<Result<T, E>>, providing a seamless way to work with asynchronous operations that may fail.
export class ResultAsync < T , E > implements PromiseLike < Result < T , E >> {
private _promise : Promise < Result < T , E >>
constructor ( res : Promise < Result < T , E >>) {
this . _promise = res
}
// ...
}
Source: result-async.ts:22-27
The key insight: ResultAsync is essentially a Promise<Result<T, E>> with additional methods that let you work with the Result without having to await it first.
Why ResultAsync?
The Problem with Async Results
When working with promises and results together, you’d normally have to write:
// Awkward: need to await before using Result methods
const promise : Promise < Result < User , Error >> = fetchUser ( id )
const result = await promise
const transformed = result . map ( user => user . name )
The Solution
ResultAsync lets you chain operations without awaiting:
// Clean: chain operations directly
const resultAsync : ResultAsync < User , Error > = fetchUser ( id )
const transformed = resultAsync . map ( user => user . name )
// transformed is ResultAsync<string, Error>
// await only when you need the final value
const final = await transformed
Creating ResultAsync
Method 1: okAsync and errAsync
Create a ResultAsync from a value:
import { okAsync , errAsync } from 'neverthrow'
const success = okAsync ( 42 )
// Type: ResultAsync<number, never>
const failure = errAsync ( 'Something went wrong' )
// Type: ResultAsync<never, string>
Source: result-async.ts:247-256
Method 2: fromPromise
Wrap an existing promise that may reject:
import { ResultAsync } from 'neverthrow'
// Original promise that may throw
const promise : Promise < User > = fetch ( '/api/user' ). then ( r => r . json ())
// Convert to ResultAsync
const resultAsync = ResultAsync . fromPromise (
promise ,
( error ) => `Failed to fetch user: ${ error } `
)
// Type: ResultAsync<User, string>
Source: result-async.ts:36-42
The second argument to fromPromise is an error handler that converts the unknown error into your error type E.
Method 3: fromSafePromise
Wrap a promise that you know will never reject:
import { ResultAsync } from 'neverthrow'
const delay = ( ms : number ) =>
new Promise < void >( resolve => setTimeout ( resolve , ms ))
const resultAsync = ResultAsync . fromSafePromise ( delay ( 1000 ))
// Type: ResultAsync<void, never>
Source: result-async.ts:29-34
Only use fromSafePromise when you’re absolutely certain the promise won’t reject. If it does reject, the ResultAsync will reject the promise instead of resolving to an Err.
Method 4: fromThrowable
Wrap a function that returns a promise and may throw:
import { ResultAsync } from 'neverthrow'
import { db } from './database'
// Original async function that may throw
const insertUser = async ( user : User ) : Promise < User > => {
return await db . insert ( 'users' , user )
}
// Wrap it to return ResultAsync
const safeInsertUser = ResultAsync . fromThrowable (
insertUser ,
( error ) => new DatabaseError ( error )
)
// Now it's safe to use
const result = safeInsertUser ({ name: 'Alice' , email: '[email protected] ' })
// Type: ResultAsync<User, DatabaseError>
Source: result-async.ts:45-61
fromThrowable is safer than fromPromise because it catches both synchronous throws (before the promise is returned) and asynchronous rejections.
The Thenable Behavior
ResultAsync implements PromiseLike<Result<T, E>>, which means it’s “thenable” and can be used with await and .then():
// Makes ResultAsync implement PromiseLike<Result>
then < A , B >(
successCallback ?: ( res : Result < T , E >) => A | PromiseLike < A > ,
failureCallback ?: ( reason : unknown ) => B | PromiseLike < B > ,
): PromiseLike < A | B > {
return this._promise.then( successCallback , failureCallback)
}
Source: result-async.ts:226-232
Using with async/await
async function getUserData ( id : number ) {
// ResultAsync can be awaited like a regular Promise
const result = await fetchUser ( id )
// ^ Type: Result<User, Error>
if ( result . isOk ()) {
return result . value
} else {
throw result . error
}
}
Using with .then()
fetchUser ( id )
. then (( result : Result < User , Error >) => {
result . match (
( user ) => console . log ( 'Success:' , user ),
( error ) => console . error ( 'Error:' , error )
)
})
Mixing with Promise.all
const [ user , posts , comments ] = await Promise . all ([
fetchUser ( id ),
fetchPosts ( id ),
fetchComments ( id ),
])
// All three are Result<T, E> types
Methods: Sync vs Async Parameters
Many ResultAsync methods accept both synchronous and asynchronous functions:
map - Synchronous or Asynchronous
map < A >( f : ( t : T ) => A | Promise < A > ): ResultAsync < A , E >
Source: result-async.ts:89-99
// Synchronous transformation
const result1 = fetchUser ( id )
. map ( user => user . name )
// Returns immediately with ResultAsync<string, Error>
// Asynchronous transformation
const result2 = fetchUser ( id )
. map ( async ( user ) => {
const avatar = await fetchAvatar ( user . avatarId )
return { ... user , avatar }
})
// Returns ResultAsync<UserWithAvatar, Error>
mapErr < U >( f : ( e : E ) => U | Promise < U > ): ResultAsync < T , U >
Source: result-async.ts:149-159
const result = fetchUser ( id )
. mapErr ( error => `User fetch failed: ${ error . message } ` )
// ResultAsync<User, string>
andThen - Chain Dependent Operations
andThen < U , F >(
f : ( t : T ) => Result < U , F > | ResultAsync < U , F >
): ResultAsync < U , E | F >
Source: result-async.ts:161-180
const result = fetchUser ( id )
. andThen ( user => validateUser ( user )) // Returns Result<User, ValidationError>
. andThen ( user => saveUser ( user )) // Returns ResultAsync<void, SaveError>
// Final type: ResultAsync<void, FetchError | ValidationError | SaveError>
andThen accepts both Result and ResultAsync return types, automatically handling the conversion.
Chaining Operations
One of the most powerful features of ResultAsync is the ability to chain multiple asynchronous operations:
const result = fetchUser ( id )
. map ( user => user . email ) // Extract email
. andThen ( email => validateEmail ( email )) // Validate (may fail)
. andThen ( email => sendWelcomeEmail ( email )) // Send email (may fail)
. map (() => 'Email sent successfully' ) // Transform success
. mapErr ( error => `Failed: ${ error } ` ) // Transform error
// Type: ResultAsync<string, string>
// Handle the final result
await result . match (
( message ) => console . log ( message ),
( error ) => console . error ( error )
)
Side Effects with andTee and orTee
andTee - Side Effects on Success
Execute a side effect on success without changing the value:
andTee ( f : ( t : T ) => unknown ): ResultAsync < T , E >
Source: result-async.ts:117-131
const result = fetchUser ( id )
. andTee ( user => console . log ( 'Fetched user:' , user . name )) // Log but don't change
. andThen ( user => saveUser ( user )) // Still has full user object
orTee - Side Effects on Error
Execute a side effect on error without changing the error:
orTee ( f : ( t : E ) => unknown ): ResultAsync < T , E >
Source: result-async.ts:133-147
const result = fetchUser ( id )
. orTee ( error => logError ( 'User fetch failed' , error )) // Log error
. orElse (() => fetchUserFromCache ( id )) // Try recovery
Use andTee and orTee for logging, metrics, or other side effects that shouldn’t affect your main logic flow.
Advanced Patterns
Pattern 1: Parallel Operations with combine
import { ResultAsync } from 'neverthrow'
const combined = ResultAsync . combine ([
fetchUser ( 1 ),
fetchUser ( 2 ),
fetchUser ( 3 ),
])
// Type: ResultAsync<[User, User, User], Error>
combined . match (
([ user1 , user2 , user3 ]) => console . log ( 'All users fetched' ),
( error ) => console . error ( 'At least one fetch failed:' , error )
)
Source: result-async.ts:63-73
Pattern 2: Collecting All Errors
const combined = ResultAsync . combineWithAllErrors ([
fetchUser ( 1 ),
fetchUser ( 2 ),
fetchUser ( 3 ),
])
// Type: ResultAsync<[User, User, User], Error[]>
// If users 1 and 3 fail, you get all errors
await combined . match (
( users ) => console . log ( 'All succeeded' ),
( errors ) => console . error ( 'Errors:' , errors ) // Array of all errors
)
Source: result-async.ts:75-87
Pattern 3: Sequential with Error Recovery
const result = fetchFromPrimaryDB ( id )
. orElse (() => fetchFromSecondaryDB ( id )) // Fallback 1
. orElse (() => fetchFromCache ( id )) // Fallback 2
. orElse (() => okAsync ( defaultUser )) // Final fallback
const userName = await fetchUser ( id )
. map ( user => user . name )
. unwrapOr ( 'Anonymous' )
// Type: string (not Result!)
Comparison: Promise of Result vs ResultAsync
Promise of Result
ResultAsync
async function processUser ( id : number ) {
const userResult = await fetchUser ( id )
if ( userResult . isErr ()) {
return err ( userResult . error )
}
const validResult = await validateUser ( userResult . value )
if ( validResult . isErr ()) {
return err ( validResult . error )
}
const saveResult = await saveUser ( validResult . value )
return saveResult
}
function processUser ( id : number ) {
return fetchUser ( id )
. andThen ( validateUser )
. andThen ( saveUser )
}
Visual Flow
Chaining is Efficient
// This creates only ONE promise
const result = fetchUser ( id )
. map ( u => u . email )
. map ( e => e . toLowerCase ())
. map ( e => e . trim ())
// Internally, all transformations are applied in a single .then()
Avoid Unnecessary Awaits
// ❌ Bad: Unnecessary await in the middle
async function process ( id : number ) {
const user = await fetchUser ( id ) // Breaks the chain
return user . map ( u => u . name )
}
// ✅ Good: Keep the chain intact
function process ( id : number ) {
return fetchUser ( id )
. map ( u => u . name )
}
Type Signatures from Source
// Constructor
class ResultAsync < T , E > implements PromiseLike < Result < T , E >> {
constructor ( res : Promise < Result < T , E >>)
}
// Static constructors
function okAsync < T , E = never >( value : T ) : ResultAsync < T , E >
function errAsync < T = never , E = unknown >( err : E ) : ResultAsync < T , E >
// Static methods
static fromPromise < T , E >(
promise : Promise < T >,
errorFn : ( e : unknown ) => E
) : ResultAsync < T , E >
static fromSafePromise < T , E = never >(
promise : Promise < T >
) : ResultAsync < T , E >
static fromThrowable < A extends readonly any [], R , E >(
fn : ( ... args : A ) => Promise < R >,
errorFn ?: ( err : unknown ) => E
) : ( ... args : A ) => ResultAsync < R , E >
// Instance methods
map < A >( f : ( t : T ) => A | Promise < A >) : ResultAsync < A , E >
mapErr < U >( f : ( e : E ) => U | Promise < U >) : ResultAsync < T , U >
andThen < U , F >( f : ( t : T ) => Result < U , F > | ResultAsync < U , F >) : ResultAsync < U , E | F >
orElse < U , A >( f : ( e : E ) => Result < U , A > | ResultAsync < U , A >) : ResultAsync < U | T , A >
match < A , B = A >( ok : ( t : T ) => A , err : ( e : E ) => B ) : Promise < A | B >
unwrapOr < A >( t : A ) : Promise < T | A >
Next Steps
Result Type Learn about the synchronous Result type
Error Handling Philosophy Understand why encoding errors in types is better than throwing