-
Notifications
You must be signed in to change notification settings - Fork 0
Async Operations
Modern C# code often uses async/await, for example when calling a database or a web API. Resultron fully supports this: almost every method has an ...Async twin (MatchAsync, MapAsync, BindAsync), in addition to TryAsync.
Use this for an asynchronous action that does not return a value, like saving something:
var result = await Result.TryAsync(async () =>
{
await _repository.SaveAsync(entity);
});
result.Match(
onSuccess: () => Console.WriteLine("Saved."),
onFailure: error => Console.WriteLine($"Error: {error.Description}")
);If SaveAsync throws an exception, it is automatically caught and converted into a failed Result, exactly like the synchronous Try.
Use this when the asynchronous action returns a value:
Result<User> result = await Result<User>.TryAsync(async () =>
{
return await _repository.GetByIdAsync(id);
});
result.Match(
onSuccess: user => Console.WriteLine($"Found: {user.Name}"),
onFailure: error => Console.WriteLine($"Error: {error.Description}")
);Resultron now provides full async equivalents of Map and Bind, so you can build a chain where each step itself is asynchronous, without breaking out of the chain to await manually in between:
-
MapAsync— async version ofMap. Use it for a step that transforms a value but does not return aResultby itself (it just happens to beasync). -
BindAsync— async version ofBind. Use it for a step that returns aResult/Result<T>wrapped in aTask(an async step that can itself succeed or fail).
Both exist on Result and on Result<T>, and both return a Task, so you await the whole chain, usually once at the end.
public async Task<Result<Order>> PlaceOrderAsync(Guid userId, Guid productId)
{
var userResult = await Result<User>.TryAsync(() => _users.GetByIdAsync(userId));
var validatedResult = await userResult.BindAsync(user => ValidateUserCanOrderAsync(user));
var orderResult = await validatedResult.BindAsync(user => CreateOrderAsync(user, productId));
return await orderResult.MapAsync(async order =>
{
await _auditLog.RecordAsync(order.Id);
return order;
});
}Note: Each
...Asyncmethod returns aTask<Result<...>>, not aResult<...>directly. This is different from the synchronousMap/Bind, which can be chained one after another without anyawaitin between. WithMapAsync/BindAsync, you need toawaiteach step before calling the next one on it, as shown above. This keeps the code simple and avoids nestingTasks insideResults.
You don't have to make every step async. Mix Map/Bind (sync) and MapAsync/BindAsync (async) freely, in whatever order your logic needs:
public async Task<Result<string>> GetWelcomeMessageAsync(Guid userId)
{
var userResult = await Result<User>.TryAsync(() => _users.GetByIdAsync(userId));
return userResult
.Bind(user => ValidateUser(user)) // sync step
.Map(user => user.Name) // sync step
.Map(name => $"Welcome, {name}!"); // sync step
}public async Task<Result> DeactivateUserAsync(Guid userId)
{
var userResult = await Result<User>.TryAsync(() => _users.GetByIdAsync(userId));
return await userResult
.Bind(user => ValidateCanDeactivate(user)) // sync validation
.BindAsync(user => _users.DeactivateAsync(user.Id)); // async save
}MatchAsync lets your success/failure branches themselves be async, for example to send a notification or write to a log asynchronously:
await result.MatchAsync(
onSuccess: user => _notifier.SendWelcomeEmailAsync(user),
onFailure: error => _logger.LogErrorAsync(error.Description)
);Or, to compute a final value asynchronously:
string message = await result.MatchAsync(
onSuccess: user => Task.FromResult($"Welcome, {user.Name}!"),
onFailure: error => Task.FromResult($"Error: {error.Description}")
);| Method | Where it exists | Purpose |
|---|---|---|
Result.TryAsync(Func<Task>) |
Result |
Catch exceptions from an async action |
Result<T>.TryAsync(Func<Task<T>>) |
Result<T> |
Catch exceptions from an async function |
MatchAsync(...) |
Result, Result<T>
|
React to success/failure asynchronously |
MapAsync(...) |
Result, Result<T>
|
Transform the value asynchronously |
BindAsync(...) |
Result, Result<T>
|
Chain to another async, Result-returning step |
See the full method signatures on the Result API Reference and Result API Reference pages.
Next: see a full, realistic example in Full Example.