Overview
Maps a Result<T, E> to ResultAsync<U, E> by applying an async function to a contained Ok value, leaving an Err value untouched. This bridges synchronous Results with asynchronous operations.
Signature
class Result<T, E> {
asyncMap<U>(f: (t: T) => Promise<U>): ResultAsync<U, E>
}
Parameters
f
(t: T) => Promise<U>
required
An async function that transforms the Ok value from type T to type U. This function is only called if the Result is Ok.
Returns
Returns a ResultAsync<U, E> which can be awaited or chained with more ResultAsync methods.
- If the original Result is
Ok(value), the returned ResultAsync resolves to Ok(await f(value))
- If the original Result is
Err(error), the returned ResultAsync resolves to Err(error) without calling f
Examples
Basic Usage
import { ok, err } from 'neverthrow'
const okVal = ok(12)
const asyncResult = okVal.asyncMap((value) => {
return Promise.resolve(value * 2)
})
// asyncResult is a ResultAsync<number, never>
const result = await asyncResult
result.isOk() // true
result._unsafeUnwrap() // 24
Database Query
import { parseHeaders } from 'imaginary-http-parser'
// parseHeaders(raw: string): Result<Headers, ParseError>
async function findUserInDatabase(
authToken: string
): Promise<User> {
// ... database query
}
const asyncResult = parseHeaders(rawHeader)
.map((headerMap) => headerMap.Authorization)
.asyncMap(findUserInDatabase)
// asyncResult is ResultAsync<User, ParseError>
Skips Errors
const errVal = err<number, string>('nooooooo')
const asyncMapper = async (value: number) => {
// ... complex async logic
return value.toString()
}
const promise = errVal.asyncMap(asyncMapper)
const result = await promise
result.isErr() // true
result._unsafeUnwrapErr() // 'nooooooo'
// asyncMapper was never called
API Calls
type UserId = string
type User = { id: UserId; name: string; email: string }
function validateUserId(input: string): Result<UserId, 'InvalidFormat'> {
return input.length > 0 ? ok(input) : err('InvalidFormat')
}
async function fetchUser(id: UserId): Promise<User> {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
const userResult = validateUserId(inputString)
.asyncMap(fetchUser)
// userResult is ResultAsync<User, 'InvalidFormat'>
await userResult.match(
(user) => console.log('Fetched user:', user.name),
(error) => console.error('Error:', error)
)
Chaining Async Operations
const result = ok(5)
.map((n) => n * 2) // Result<number, never>
.asyncMap(async (n) => {
await delay(100)
return n.toString()
}) // ResultAsync<string, never>
.andThen(async (str) => {
const data = await fetchData(str)
return ok(data)
}) // ResultAsync<Data, never>
const finalResult = await result
File I/O
import { promises as fs } from 'fs'
function validatePath(path: string): Result<string, 'InvalidPath'> {
return path.length > 0 ? ok(path) : err('InvalidPath')
}
const fileContent = validatePath(userInput)
.asyncMap((path) => fs.readFile(path, 'utf-8'))
// fileContent is ResultAsync<string, 'InvalidPath'>
await fileContent.match(
(content) => console.log('File content:', content),
(error) => console.error('Error:', error)
)
Parallel Async Operations
type ImageData = { url: string; size: number }
function validateImageUrl(url: string): Result<string, 'InvalidUrl'> {
// ... validation
}
async function fetchImageMetadata(url: string): Promise<ImageData> {
// ... fetch from CDN
}
const urls = ['url1', 'url2', 'url3']
const imagePromises = urls.map((url) =>
validateImageUrl(url)
.asyncMap(fetchImageMetadata)
)
const images = await Promise.all(imagePromises)
// images is Result<ImageData, 'InvalidUrl'>[]
Real-World Authentication Flow
type Credentials = { username: string; password: string }
type SessionToken = string
function parseCredentials(
body: unknown
): Result<Credentials, 'ParseError'> {
// ... parsing logic
}
async function authenticate(
creds: Credentials
): Promise<SessionToken> {
// ... async authentication
}
const sessionResult = parseCredentials(req.body)
.asyncMap(authenticate)
await sessionResult.match(
(token) => res.json({ success: true, token }),
(error) => res.status(400).json({ success: false, error })
)
Error Propagation
// Note: asyncMap doesn't catch exceptions thrown by the async function
const result = ok('user123')
.asyncMap(async (id) => {
// If this throws, the promise will reject!
throw new Error('Database connection failed')
})
try {
await result
} catch (error) {
// Exception is NOT converted to Err automatically
console.error('Unhandled exception:', error)
}
// Use ResultAsync.fromPromise to handle exceptions:
const safeResult = ok('user123')
.asyncMap((id) =>
ResultAsync.fromPromise(
fetchUser(id),
(e) => 'FetchError'
)
)
.andThen((res) => res) // Flatten nested Result
Implementation Details
From the source code (result.ts:383-385):
asyncMap<U>(f: (t: T) => Promise<U>): ResultAsync<U, E> {
return ResultAsync.fromSafePromise(f(this.value))
}
For Err (result.ts:484-486):
asyncMap<U>(_f: (t: T) => Promise<U>): ResultAsync<U, E> {
return errAsync<U, E>(this.error)
}
Notes
- Always returns a
ResultAsync, even when starting with a synchronous Result
- The async function is only executed for
Ok values
- Errors in the original Result are propagated without awaiting anything
- The async function must return a
Promise, not a Result or ResultAsync
- For functions returning
ResultAsync, use asyncAndThen() instead
- Exceptions thrown by the async function will cause the promise to reject (not convert to Err)