Async Result
Result type for asynchronous operations
The AsyncResult type is an asynchronous version of Result - it's a Promise<Result<T, E>>. Use it when dealing with operations that can fail and are asynchronous.
What is AsyncResult?
AsyncResult<T, E> is just a type alias:
type AsyncResult<T, E> = Promise<Result<T, E>>;It represents an asynchronous operation that will eventually complete with either a success value or an error.
Creating AsyncResult
Using AsyncResult.from() Helper
Wrap an async function that might throw:
import { AsyncResult } from '@deessejs/functions';
const result = AsyncResult.from(async () => {
const data = await fetchData();
return JSON.parse(data);
});
// result is AsyncResult<any, Error>Manual Creation
Return Result.success() or Result.failure() in async functions:
import { Result } from '@deessejs/functions';
async function fetchUser(id: string): AsyncResult<User, Error> {
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
if (!user) {
return Result.failure(new Error("User not found"));
}
return Result.success(user);
} catch (error) {
return Result.failure(error as Error);
}
}Using AsyncResult
Awaiting Results
Since AsyncResult is a Promise, await it:
const result = await fetchUser("123");
if (result.isSuccess()) {
console.log("User:", result.value.name);
} else {
console.error("Error:", result.error.message);
}Pattern Matching
Combine with match():
const result = await fetchUser("123");
const message = result.match({
onSuccess: (user) => `Welcome ${user.name}!`,
onFailure: (error) => `Error: ${error.message}`,
});
console.log(message);Error Handling
Automatic Exception Catching
Use AsyncResult.from() to automatically catch thrown exceptions:
import { AsyncResult } from '@deessejs/functions';
// This function might throw
const risky = AsyncResult.from(async () => {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("Network error");
}
return await response.json();
});
// No need for try-catch - exceptions are converted to Failure
const result = await risky;Manual Try-Catch
Or use try-catch for more control:
async function fetchWithRetry(url: string): AsyncResult<Data, Error> {
try {
const response = await fetch(url);
return Result.success(await response.json());
} catch (error) {
return Result.failure(error as Error);
}
}Chaining Async Operations
Sequential Operations
async function getUserWithPosts(userId: string): AsyncResult<
{ user: User; posts: Post[] },
Error
> {
// Fetch user
const userResult = await fetchUser(userId);
if (userResult.isFailure()) {
return Result.failure(userResult.error);
}
// Fetch posts
const postsResult = await fetchPosts(userId);
if (postsResult.isFailure()) {
return Result.failure(postsResult.error);
}
// Combine results
return Result.success({
user: userResult.value,
posts: postsResult.value,
});
}Using Match for Chaining
async function processUser(userId: string): AsyncResult<string, Error> {
const userResult = await fetchUser(userId);
return userResult.match({
onSuccess: async (user) => {
// Continue with async operation
const emailResult = await sendEmail(user.email);
return emailResult.match({
onSuccess: () => Result.success("Email sent successfully"),
onFailure: (error) => Result.failure(error),
});
},
onFailure: (error) => Result.failure(error),
});
}Working with APIs
Fetch Wrapper
import { AsyncResult, Result } from '@deessejs/functions';
async function fetchAPI<T>(
url: string,
options?: RequestInit
): AsyncResult<T, Error> {
return AsyncResult.from(async () => {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json() as T;
});
}
// Usage
const result = await fetchAPI<User>("/api/users/123");
result.match({
onSuccess: (user) => console.log("User:", user),
onFailure: (error) => console.error("Failed:", error.message),
});Best Practices
1. Always Type the Error Parameter
// ✅ Good - explicit error type
async function fetchUser(id: string): AsyncResult<User, NetworkError> {
// ...
}
// ⚠️ Less clear - generic error
async function fetchUser(id: string): AsyncResult<User, Error> {
// ...
}2. Use AsyncResult.from() for Simple Cases
// ✅ Good - simple and clean
const result = AsyncResult.from(async () => {
return await fetchData();
});
// ❌ Verbose - unnecessary try-catch
const result = (async () => {
try {
return Result.success(await fetchData());
} catch (error) {
return Result.failure(error);
}
})();3. Return Early on Failure
// ✅ Good - early return
async function processUser(id: string): AsyncResult<User, Error> {
const user = await fetchUser(id);
if (user.isFailure()) {
return Result.failure(user.error);
}
const posts = await fetchPosts(id);
if (posts.isFailure()) {
return Result.failure(posts.error);
}
return Result.success({ ...user.value, posts: posts.value });
}
// ❌ Bad - nested if-else
async function processUser(id: string): AsyncResult<User, Error> {
const user = await fetchUser(id);
if (user.isSuccess()) {
const posts = await fetchPosts(id);
if (posts.isSuccess()) {
return Result.success({ ...user.value, posts: posts.value });
} else {
return Result.failure(posts.error);
}
} else {
return Result.failure(user.error);
}
}4. Handle Errors Appropriately
// ✅ Good - business logic errors as failures
async function deleteUser(id: string): AsyncResult<void, Error> {
const user = await db.users.find(id);
if (!user) {
return Result.failure(new Error("User not found"));
}
await db.users.delete(id);
return Result.success(undefined);
}
// ❌ Bad - throwing for business logic
async function deleteUser(id: string): AsyncResult<void, Error> {
const user = await db.users.find(id);
if (!user) {
throw new Error("User not found"); // Breaks flow
}
await db.users.delete(id);
return Result.success(undefined);
}Converting Between Types
Result to AsyncResult
import { Result } from '@deessejs/functions';
// Wrap a sync Result in a Promise
async function toAsyncResult<T, E>(result: Result<T, E>): AsyncResult<T, E> {
return result;
}
// Or simply
const asyncResult: AsyncResult<number, Error> = Promise.resolve(Result.success(42));AsyncResult to Result
// Convert AsyncResult to Result by awaiting
async function toResult<T, E>(asyncResult: AsyncResult<T, E>): Result<T, E> {
return await asyncResult;
}