Skip to main content
ResultAsync<T, E> is a PromiseLike<Result<T, E>>. You can await it directly to get a synchronous Result<T, E>, or chain methods on it without ever leaving the async context. Every method that exists on Result has an equivalent on ResultAsync — each returns another ResultAsync so chains stay fluent.
import { chas } from 'ts-chas';

const result: chas.Result<number, string> = await chas.okAsync(42);
// Ok(42)

Creating a ResultAsync

chas.okAsync(value)

Wraps a value or Promise<T> in a ResultAsync that resolves to Ok.
value
T | Promise<T>
required
The success value or promise to wrap.
returns
ResultAsync<T, never>
Resolves to Ok(value).
const res = chas.okAsync(42);
const result = await res; // Ok(42)

chas.errAsync(error)

Wraps an error or Promise<E> in a ResultAsync that resolves to Err.
error
E | Promise<E>
required
The error value or promise to wrap.
returns
ResultAsync<never, E>
Resolves to Err(error).
const res = chas.errAsync('request failed');
const result = await res; // Err('request failed')

chas.fromPromise(promise, mapErr?)

Wraps a native Promise in a ResultAsync. Resolves to Ok on fulfillment and Err on rejection.
promise
Promise<T>
required
The promise to wrap.
mapErr
(error: unknown) => E
Maps the rejection reason to your error type. When omitted, the error is typed as unknown.
returns
ResultAsync<T, E>
A ResultAsync wrapping the promise outcome.
const res = chas.fromPromise(
  fetch('/api/user').then(r => r.json()),
  (e) => `Fetch failed: ${e}`
);

ResultAsync.fromResult(syncResult)

Lifts a synchronous Result into ResultAsync.
syncResult
Result<T, E>
required
The synchronous result to wrap.
returns
ResultAsync<T, E>
A ResultAsync that immediately resolves to the provided result.
const sync = chas.ok(42);
const async = chas.ResultAsync.fromResult(sync);
const result = await async; // Ok(42)

ResultAsync.fromSafePromise(promise)

Wraps a promise that is guaranteed not to reject. No mapErr is needed.
promise
Promise<T>
required
A promise that will not reject.
returns
ResultAsync<T, never>
Resolves to Ok(value).
const res = chas.ResultAsync.fromSafePromise(Promise.resolve(42));
const result = await res; // Ok(42)

Instance methods

All methods below return a ResultAsync, so you can chain them without await until you need the final value.

.map(fn)

Transforms the Ok value. Err passes through unchanged.
fn
(value: T) => U | Promise<U>
required
Function applied to the Ok value. May be async.
returns
ResultAsync<U, E>
Ok(fn(value)) or the original Err.
const res = chas.okAsync(5).map(v => v * 2);
const result = await res; // Ok(10)

.mapErr(fn)

Transforms the Err error. Ok passes through unchanged.
fn
(error: E) => F | Promise<F>
required
Function applied to the Err error. May be async.
returns
ResultAsync<T, F>
The original Ok or Err(fn(error)).
const res = chas.errAsync('not_found').mapErr(e => e.toUpperCase());
const result = await res; // Err('NOT_FOUND')

.andThen(fn)

Chains another Result- or ResultAsync-returning function. Calls fn with the Ok value, or short-circuits on Err.
fn
(value: T) => Result<U, F> | ResultAsync<U, F>
required
Function returning a new result. May return sync or async.
returns
ResultAsync<U, E | F>
The chained result, or the original Err.
const res = chas.okAsync(5).andThen(v => chas.okAsync(v * 2));
const result = await res; // Ok(10)

.orElse(fn)

Recovers from an Err by calling fn with the error. Ok passes through unchanged.
fn
(error: E) => Result<T2, F> | ResultAsync<T2, F>
required
Recovery function returning a new result.
returns
ResultAsync<T | T2, F>
The original Ok, or the recovery result.
const res = chas.errAsync('timeout').orElse(() => chas.okAsync('default'));
const result = await res; // Ok('default')

.tap(fn)

Runs a side effect when Ok. Returns the original result unchanged.
fn
(value: T) => void | Promise<void>
required
Side-effect function. May be async.
returns
ResultAsync<T, E>
The original result, unmodified.
const res = await fetchUser()
  .tap(async u => await auditLog.record(u.id));

.tapErr(fn)

Runs a side effect when Err. Returns the original result unchanged.
fn
(error: E) => void | Promise<void>
required
Side-effect function. May be async.
returns
ResultAsync<T, E>
The original result, unmodified.
const res = await fetchUser()
  .tapErr(async e => await errorLog.send(e));

.match({ ok, err })

Exhaustively handles both branches and resolves to a plain value.
ok
(value: T) => U | Promise<U>
required
Handler called when the result is Ok.
err
(error: E) => F | Promise<F>
required
Handler called when the result is Err.
returns
Promise<U | F>
A standard Promise (not a ResultAsync) resolving to the matched handler’s return value.
const message = await chas.okAsync(42).match({
  ok: v => `Got ${v}`,
  err: e => `Failed: ${e}`,
});
// 'Got 42'

.unwrap()

Returns a Promise that resolves to the Ok value, or throws the Err error.
returns
Promise<T>
Resolves to the Ok value.
Throws the contained error if the result is Err. Prefer .unwrapOr() or .match() in production code.
const value = await chas.okAsync(5).unwrap(); // 5

.unwrapOr(defaultValue)

Returns a Promise that resolves to the Ok value, or defaultValue if Err.
defaultValue
T
required
Fallback value returned when the result is Err.
returns
Promise<T>
Resolves to the Ok value or the default.
const value = await chas.errAsync('missing').unwrapOr(0); // 0

.pipe(fn1, fn2, ...)

Passes the ResultAsync through a sequence of functions, identical in behaviour to Result#pipe.
const add5 = (r: chas.ResultAsync<number, never>) => r.map(v => v + 5);
const double = (r: chas.ResultAsync<number, never>) => r.map(v => v * 2);

const result = await chas.okAsync(1).pipe(add5, double);
// Ok(12)

.context(ctx)

Attaches a debug label or metadata object to the error when Err. No-op when Ok.
ctx
string | Record<string, unknown>
required
A description string or metadata object for the current step.
returns
ResultAsync<T, E>
The same result with context prepended to error._context (if Err).
const result = await fetchUser(id)
  .context('fetching user')
  .andThen(u => validatePermissions(u))
  .context('checking permissions');

.catchTag(target, handler)

Catches a specific tagged error variant by _tag or error factory, and removes it from the error union. Unmatched tags pass through unchanged.
target
string | ErrorFactory
required
The _tag string to match, or an error factory.
handler
(error: MatchedError) => Result<T2, E2> | ResultAsync<T2, E2>
required
Recovery function.
returns
ResultAsync<T | T2, Exclude<E, { _tag: ... }> | E2>
A result with the matched tag removed from the error union.
await fetchUser(id)
  .catchTag('NotFound', () => chas.ok(GUEST_USER));
// ResultAsync<User | GuestUser, Unauthorized>

.tapTag(target, handler)

Runs a side effect for a specific tagged error. The original result is returned unchanged.
target
string | ErrorFactory
required
The _tag string to match, or an error factory.
handler
(error: MatchedError) => void | Promise<void>
required
Side-effect function called when the tag matches.
returns
ResultAsync<T, E>
The original result, unmodified.
await fetchUser(id)
  .tapTag('Unauthorized', () => redirectToLogin());

Complete example

import { chas } from 'ts-chas';

interface User { id: number; name: string }

function fetchUser(id: number): chas.ResultAsync<User, string> {
  return chas.fromPromise(
    fetch(`/api/users/${id}`).then(r => {
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      return r.json() as Promise<User>;
    }),
    (e) => `Fetch error: ${e}`
  );
}

const result = await fetchUser(1)
  .map(user => ({ ...user, name: user.name.toUpperCase() }))
  .mapErr(e => ({ code: 'FETCH_FAILED', detail: e }))
  .tap(user => console.log('Fetched:', user.name));

result.match({
  ok: user => console.log('User:', user.name),  // 'ALICE'
  err: e => console.error('Failed:', e.detail),
});