Functions

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;
}

See Also

  • Result - Synchronous version of AsyncResult
  • Try - For wrapping code that might throw
  • Outcome - Advanced result system with causes and exceptions

On this page