Skip to content

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.