Async Patterns
4.2.1. 🛡️ Exception Wrapping — Try / TryAsync
Safely execute code that may throw — exceptions become ExceptionError in a failed Result<T>:
// Sync — wraps any thrown exception
Result<int> parsed = Result<int>.Try(() => int.Parse(input));
Result<User> user = Result<User>.Try(() => GetUser(id));
// Custom error handler — map exception to a domain error
Result<User> result = Result<User>.Try(
() => JsonSerializer.Deserialize<User>(json),
ex => new ValidationError("body", $"Invalid JSON: {ex.Message}"));
// Async
Result<User> result = await Result<User>.TryAsync(
async () => await _api.FetchUserAsync(id));
// Async with custom handler
Result<User> result = await Result<User>.TryAsync(
async () => await _repo.GetAsync(id),
ex => new NotFoundError($"User {id} not found"));
4.2.2. ⏳ CancellationToken Support
All *Async methods accept CancellationToken cancellationToken = default:
// Pass through from your endpoint/controller
Result<User> result = await Result<User>.TryAsync(
async () => await _repo.GetAsync(id),
cancellationToken: ct);
// Bind / Map / Tap async chains also accept ct
Result<UserDto> dto = await result
.BindAsync(u => _mapper.MapAsync(u, ct))
.TapAsync(d => _cache.SetAsync(d, ct));
4.2.3. 🪤 Inline Exception Handling — Catch<TException> / CatchAsync<TException>
When a pipeline step may throw a specific exception type, Catch converts the ExceptionError wrapping that exception into a domain error — without breaking the pipeline:
// Convert HttpRequestException to a domain NotFoundError
Result<User> user = await Result<User>.TryAsync(() => _api.FetchUserAsync(id))
.Catch<HttpRequestException>(ex => new NotFoundError("User", id));
// Convert DbException to ConflictError
Result<Order> order = await Result<Order>.TryAsync(() => _db.InsertOrderAsync(dto))
.Catch<DbException>(ex => new ConflictError("Order", ex.Message));
// Async handler
Result<User> result = await Result<User>.TryAsync(() => _api.FetchUserAsync(id))
.CatchAsync<HttpRequestException>(async ex =>
{
await _telemetry.TrackExceptionAsync(ex);
return new NotFoundError("User", id);
});
The ExceptionError is replaced in-place — preserving its position in the error list. Other errors are untouched. If there is no matching exception error, or the result is successful, it passes through unchanged.