En el post anterior, implementamos una clase Result<T> con métodos monádicos (Map, FlatMap, OrElse).
Ahora combinaremos esa idea con Task<T> para obtener un flujo asíncrono y seguro ante errores, sin try/catch y sin anidar await.
Supongamos que tenemos funciones asíncronas que pueden fallar:
Task<Result<User>> GetUserAsync(int id);
Task<Result<Order>> GetOrderAsync(User user);
Task<Result<Invoice>> CreateInvoiceAsync(Order order);
Queremos encadenarlas de forma limpia, propagando el error automáticamente, sin esto 👇:
var userResult = await GetUserAsync(id);
if (userResult is ErrorResult<User>) return userResult;
var orderResult = await GetOrderAsync(userResult.Value);
// etc...
Creamos extensiones que aplanan el contexto doble (Task<Result<T>>):
public static class TaskResultExtensions
{
public static async Task<Result<U>> Map<T, U>(
this Task<Result<T>> task, Func<T, U> f)
{
var result = await task;
return result is OkResult<T> ok ? Result<U>.Ok(f(ok.Value))
: Result<U>.Error(((ErrorResult<T>)result).Message);
}
public static async Task<Result<U>> FlatMap<T, U>(
this Task<Result<T>> task, Func<T, Task<Result<U>>> f)
{
var result = await task;
return result is OkResult<T> ok ? await f(ok.Value)
: Result<U>.Error(((ErrorResult<T>)result).Message);
}
}
Veamos como usarlo:
var invoice = await GetUserAsync(10)
.FlatMap(GetOrderAsync)
.FlatMap(CreateInvoiceAsync)
.Map(invoice => invoice.WithDiscount(10))
.FlatMap(SaveInvoiceAsync)
.OrElse(Result<Invoice>.Error("No se pudo generar factura"));
¿Cuál es la ventaja?
- Si cualquier paso falla, la cadena se corta automáticamente
- No se necesitan try/catch ni comprobaciones manuales
- El código se lee de arriba a abajo, como una secuencia lógica de pasos
Veamos otro ejemplo:
public async Task<Result<string>> GenerateInvoice(int userId)
{
return await GetUserAsync(userId)
.FlatMap(GetOrderAsync)
.FlatMap(CreateInvoiceAsync)
.Map(invoice => invoice.Id.ToString());
}
Si alguna función devuelve un Result.Error, ese error se propaga sin ejecutar los pasos siguientes.
El resultado final es un Result<string> con éxito o mensaje de error.
