> [Agent navigation index](/llms.txt)

# Compose asynchronous typed results

Consume Brix's awaitable `ResultPromise`, adapt application promises at their boundary, and compose typed asynchronous outcomes fluently.

## Consume ResultPromise

Asynchronous Brix APIs use `ResultPromise<T, E>`. Expected failures resolve to `Err`; unexpected Promise failures still reject.

Await it when you need branch properties:

```ts
const result = await contentful.getEntry("page", entryId);

if (result.isErr()) return { error: result.error.tag };
return result.value.fields;

```

Or chain directly and await a terminal method:

```ts
const title = await contentful
  .getEntry("page", entryId)
  .map((entry) => entry.fields.title)
  .tapError((error) => logger.warn({ error }))
  .unwrapOr("Untitled");

```

`andThen` accepts a callback returning a synchronous `Result`. `andThenAsync` accepts `Promise<Result>`, including another `ResultPromise`:

```ts
const asset = await contentful
  .getEntry("page", entryId)
  .andThenAsync((entry) => contentful.getAsset(entry.fields.heroAssetId))
  .unwrap();

```

The resulting error type is the union of errors from both operations.

## Adapt an application promise

Wrap an application-owned `Promise<Result<T, E>>` once at its API boundary:

```ts
import { Result, resultPromise, type ResultPromise } from "brix/result";

function loadBooking(id: string): ResultPromise<Booking, BookingNotFoundError> {
  return resultPromise(
    repository
      .find(id)
      .then((booking) =>
        booking ? Result.ok(booking) : Result.err(new BookingNotFoundError({ bookingId: id })),
      ),
  );
}

```

`resultPromise` decorates the native promise in place with non-enumerable methods and is idempotent. It does not turn a rejected promise into an `Err`; catch and model only failures your application deliberately classifies as expected.

## ResultPromise operations

The exact Brix signatures are:

```ts
type ResultPromise<T, E> = Promise<Result<T, E>> & {
  map<U>(fn: (value: T) => U): ResultPromise<U, E>;
  mapError<E2>(fn: (error: E) => E2): ResultPromise<T, E2>;
  tryRecover<U, E2>(fn: (error: E) => Result<U, E2>): ResultPromise<T | U, E2>;
  tryRecoverAsync<U, E2>(fn: (error: E) => Promise<Result<U, E2>>): ResultPromise<T | U, E2>;
  andThen<U, E2>(fn: (value: T) => Result<U, E2>): ResultPromise<U, E | E2>;
  andThenAsync<U, E2>(fn: (value: T) => Promise<Result<U, E2>>): ResultPromise<U, E | E2>;
  tap(fn: (value: T) => void): ResultPromise<T, E>;
  tapAsync(fn: (value: T) => Promise<void>): ResultPromise<T, E>;
  tapError(fn: (error: E) => void): ResultPromise<T, E>;
  tapErrorAsync(fn: (error: E) => Promise<void>): ResultPromise<T, E>;
  tapBoth(handlers: { ok: (value: T) => void; err: (error: E) => void }): ResultPromise<T, E>;
  tapBothAsync(handlers: {
    ok: (value: T) => Promise<void>;
    err: (error: E) => Promise<void>;
  }): ResultPromise<T, E>;
  match<U>(handlers: { ok: (value: T) => U; err: (error: E) => U }): Promise<U>;
  unwrap(message?: string): Promise<T>;
  unwrapOr<D>(fallback: D): Promise<T | D>;
};

```

Recovery replaces the original error type while preserving either original or recovered success. Async callbacks are awaited before the chain proceeds. Calling native `.then()`, `.catch()`, or `.finally()` deliberately returns an ordinary Promise and leaves the fluent Result API.

## Test both branches

```ts
import { expect, it } from "brix/test";

it("reports a missing booking", () => {
  const result = findBooking("missing");
  expect(result.isErr()).toBe(true);
  if (result.isErr()) {
    expect(result.error).toMatchObject({ bookingId: "missing" });
  }
});

```

For HTTP request results and retries, continue with [API routes](/docs/api-routes). For provider-specific result unions, see the [Contentful guide](/docs/contentful), [testing](/docs/testing), and [API reference](/docs/api-reference).
