Async Support¶
Overview¶
TinyResult provides comprehensive support for asynchronous operations through the Result<T>
type. This allows you to work with asynchronous code in a clean and type-safe way, while maintaining the benefits of the Result Pattern.
Basic Async Operations¶
1. Creating Async Results¶
// Async success
public async Task<Result<User>> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return user != null
? Result<User>.Success(user)
: Result<User>.Failure(ErrorCode.NotFound, $"User {id} not found");
}
// Async failure
public async Task<Result<User>> CreateUserAsync(User user)
{
try
{
var createdUser = await _repository.CreateAsync(user);
return Result<User>.Success(createdUser);
}
catch (Exception ex)
{
return Result<User>.Failure(
ErrorCode.DatabaseError,
"Failed to create user",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
2. Handling Async Results¶
// Using async/await
public async Task<IActionResult> GetUser(int id)
{
var result = await GetUserAsync(id);
return result.Match(
user => Ok(user),
error => StatusCode(GetStatusCode(error.Code), error.Message)
);
}
// Using ContinueWith
GetUserAsync(1)
.ContinueWith(task =>
{
var result = task.Result;
result.Match(
user => Console.WriteLine($"User found: {user.Name}"),
error => Console.WriteLine($"Error: {error.Message}")
);
});
Advanced Async Features¶
1. Async Pipeline¶
public async Task<Result<string>> ProcessUserAsync(int id)
{
return await GetUserAsync(id)
.MapAsync(user => GetUserProfileAsync(user))
.MapAsync(profile => FormatProfileAsync(profile))
.OnSuccessAsync(formatted => Console.WriteLine($"Formatted: {formatted}"))
.OnFailureAsync(error => Console.WriteLine($"Error: {error.Message}"));
}
2. Async Validation¶
public async Task<Result<User>> ValidateAndCreateUserAsync(User user)
{
// Async validation
var validationResult = await ValidateUserAsync(user);
if (validationResult.IsFailure)
{
return Result<User>.Failure(validationResult);
}
// Async creation
return await CreateUserAsync(user);
}
private async Task<ValidationResult> ValidateUserAsync(User user)
{
var result = ValidationResult.Create();
// Async name validation
if (await IsNameTakenAsync(user.Name))
{
result.AddError("Name", "Username is already taken");
}
// Async email validation
if (await IsEmailValidAsync(user.Email))
{
result.AddError("Email", "Email is invalid");
}
return result;
}
3. Async Error Handling¶
public async Task<Result<User>> GetUserWithRetryAsync(int id)
{
return await GetUserAsync(id)
.CatchAsync(async error =>
{
if (error.Code == ErrorCode.NetworkError)
{
// Retry after delay
await Task.Delay(1000);
return await GetUserAsync(id);
}
return Result<User>.Failure(error);
});
}
Best Practices¶
1. Use Async All the Way¶
// Avoid
public async Task<Result<User>> GetUserAsync(int id)
{
var user = _repository.GetById(id); // Synchronous call
return user != null
? Result<User>.Success(user)
: Result<User>.Failure(ErrorCode.NotFound);
}
// Prefer
public async Task<Result<User>> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return user != null
? Result<User>.Success(user)
: Result<User>.Failure(ErrorCode.NotFound);
}
2. Handle Async Exceptions¶
// Avoid
public async Task<Result<User>> GetUserAsync(int id)
{
var user = await _repository.GetByIdAsync(id);
return Result<User>.Success(user);
}
// Prefer
public async Task<Result<User>> GetUserAsync(int id)
{
try
{
var user = await _repository.GetByIdAsync(id);
return Result<User>.Success(user);
}
catch (Exception ex)
{
return Result<User>.Failure(
ErrorCode.Unknown,
"An error occurred while getting user",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
3. Use ConfigureAwait¶
public async Task<Result<User>> GetUserAsync(int id)
{
try
{
var user = await _repository.GetByIdAsync(id).ConfigureAwait(false);
return Result<User>.Success(user);
}
catch (Exception ex)
{
return Result<User>.Failure(
ErrorCode.Unknown,
"An error occurred while getting user",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
Common Use Cases¶
1. API Controllers¶
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var result = await _userService.GetUserAsync(id);
return result.Match(
user => Ok(user),
error => StatusCode(GetStatusCode(error.Code), error.Message)
);
}
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] User user)
{
var result = await _userService.CreateUserAsync(user);
return result.Match(
createdUser => CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, createdUser),
error => StatusCode(GetStatusCode(error.Code), error.Message)
);
}
}
2. Database Operations¶
public class UserRepository
{
private readonly DbContext _context;
public UserRepository(DbContext context)
{
_context = context;
}
public async Task<Result<User>> GetUserAsync(int id)
{
try
{
var user = await _context.Users.FindAsync(id);
return user != null
? Result<User>.Success(user)
: Result<User>.Failure(ErrorCode.NotFound, $"User {id} not found");
}
catch (Exception ex)
{
return Result<User>.Failure(
ErrorCode.DatabaseError,
"Failed to get user",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
public async Task<Result<User>> CreateUserAsync(User user)
{
try
{
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
return Result<User>.Success(user);
}
catch (Exception ex)
{
return Result<User>.Failure(
ErrorCode.DatabaseError,
"Failed to create user",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
}
3. External Service Calls¶
public class ExternalService
{
private readonly HttpClient _httpClient;
public ExternalService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Result<WeatherData>> GetWeatherAsync(string city)
{
try
{
var response = await _httpClient.GetAsync($"weather/{city}");
if (!response.IsSuccessStatusCode)
{
return Result<WeatherData>.Failure(
ErrorCode.NetworkError,
$"Failed to get weather data: {response.StatusCode}"
);
}
var content = await response.Content.ReadAsStringAsync();
var weatherData = JsonSerializer.Deserialize<WeatherData>(content);
return Result<WeatherData>.Success(weatherData);
}
catch (Exception ex)
{
return Result<WeatherData>.Failure(
ErrorCode.Unknown,
"Failed to get weather data",
new Dictionary<string, object> { { "Exception", ex } }
);
}
}
}