Async Operations
Working with Promises and async/await in Result chains.
AsyncResult Type
A type alias for a Promise that resolves to a Result:
typescript
type AsyncResult<T, E> = Promise<Result<T, E>>Creating Async Results
fromPromise()
Wrap Promises that might reject:
typescript
const user = await Result.fromPromise(
() => fetch('/api/user').then(r => r.json())
)
if (user.isOk()) {
console.log(user.unwrap())
}With custom error handling:
typescript
type NetworkError = { type: 'network'; status?: number }
const data = await Result.fromPromise(
() => fetch('/api/data').then(r => r.json()),
(err): NetworkError => ({
type: 'network',
status: err instanceof Response ? err.status : undefined
})
)With async function:
typescript
const result = await Result.fromPromise(async () => {
const response = await fetch('/api/data')
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
})Async Transformations
mapAsync()
Transform values asynchronously:
typescript
await Result.ok(userId)
.mapAsync(async (id) => await fetchUser(id))Auto-flattens if the mapper returns a Result:
typescript
await Result.ok(input)
.mapAsync(async (x) => {
const result = await validate(x)
return result ? Result.ok(x) : Result.err('invalid')
})mapErrAsync()
Transform errors asynchronously:
typescript
await result.mapErrAsync(async (error) => ({
...error,
context: await fetchContext(),
timestamp: Date.now()
}))mapOrAsync()
Transform with fallback:
typescript
const value = await Result.ok(5).mapOrAsync(
async (x) => await expensiveComputation(x),
defaultValue
)mapOrElseAsync()
Transform using appropriate async mapper for each case:
typescript
const value = await result.mapOrElseAsync(
async (x) => x * 2,
async (e) => -1
)Async Chaining
andThenAsync()
Chain async operations that return Results:
typescript
const result = await Result.ok(userId)
.andThenAsync(async (id) => {
const user = await fetchUser(id)
return user ? Result.ok(user) : Result.err('not found')
})orElseAsync()
Async error recovery:
typescript
const result = await fetchFromPrimary()
.orElseAsync(async () => await fetchFromCache())
.orElseAsync(async () => await fetchFromAPI())andAsync() / orAsync()
Work with Promises directly:
typescript
const nextOp: AsyncResult<number, string> = Promise.resolve(Result.ok(42))
await result.andAsync(nextOp) // If Ok, return Promise
await result.orAsync(nextOp) // If Err, return PromiseParallel Operations
Multiple Independent Operations
typescript
async function loadDashboard(userId: string) {
const results = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchNotifications(userId)
])
return Result.all(results).map(([user, posts, notifs]) => ({
user, posts, notifs
}))
}Collect All Without Failing
typescript
const results = await Promise.all([
fetchUser(id),
fetchPosts(id),
fetchComments(id)
])
const settled = Result.allSettled(results).unwrap()
settled.forEach((result) => {
if (result.status === 'ok') {
process(result.value)
} else {
logError(result.reason)
}
})Real-World Pipeline
Complex async workflow:
typescript
async function processUserRegistration(
formData: FormData
): AsyncResult<User, RegistrationError> {
return Result.ok(formData)
// Validate locally
.andThen((data) => validateForm(data))
// Check if user exists (async)
.andThenAsync(async (data) => {
const exists = await checkUserExists(data.email)
return exists
? Result.err({ type: 'user_exists' })
: Result.ok(data)
})
// Hash password (async)
.andThenAsync(async (data) => {
const hash = await bcrypt.hash(data.password)
return Result.ok({ ...data, password: hash })
})
// Create user in database (async)
.andThenAsync(async (data) => {
const user = await db.users.create(data)
return user ? Result.ok(user) : Result.err({ type: 'db_error' })
})
}Retry with Exponential Backoff
typescript
async function fetchWithRetry<T>(
url: string,
maxRetries: number = 3
): AsyncResult<T, Error> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const result = await Result.fromPromise(
() => fetch(url).then(r => r.json())
)
if (result.isOk()) return result
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000
await new Promise(resolve => setTimeout(resolve, delay))
}
}
return Result.err(new Error('Max retries exceeded'))
}Error Handling with Abort Signals
typescript
async function fetchWithTimeout(
url: string,
timeout: number = 5000
): AsyncResult<unknown, Error> {
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), timeout)
const result = await Result.fromPromise(
() => fetch(url, { signal: controller.signal }).then(r => r.json()),
() => new Error('Request timeout')
)
clearTimeout(id)
return result
}Mixing Sync and Async
typescript
const result = await Result.ok(data)
.map(parseJSON) // Sync
.andThenAsync(async (d) => { // Async
const valid = await validate(d)
return valid ? Result.ok(d) : Result.err('invalid')
})
.mapErr(enrichError) // Sync
.orElseAsync(async () => { // Async
return Result.ok(await fetchDefault())
})Common Pitfalls
Forgetting await
typescript
// ✗ Wrong: result is Promise<Result>, not Result
const result = Result.ok(1).mapAsync(async x => x * 2)
console.log(result.unwrap()) // Runtime error
// ✓ Correct: await to get Result
const result = await Result.ok(1).mapAsync(async x => x * 2)
console.log(result.unwrap()) // 2Not Returning Result in andThenAsync()
typescript
// ✗ Wrong: andThenAsync expects a Result return
.andThenAsync(async x => x * 2)
// ✓ Correct: return a Result
.andThenAsync(async x => Result.ok(x * 2))Best Practices
- Always
awaitasync Result methods - Use
fromPromise()to wrap Promises - Chain with
andThenAsync()for sequential async operations - Use
Promise.all()+Result.all()for parallel operations - Add timeouts for network requests
- Retry with backoff for flaky operations
- Use
inspectErr()for logging side effects
Next Steps
- Error Handling — Recovery strategies
- Best Practices — General recommendations