Result
Result<T, E> is the type used for
returning and propagating errors. It is a type with the variants,
Ok<T>, representing success and containing
a value, and Err<E>, representing error
and containing an error value.
Unlike in Rust, in JavaScript errors may occur at any time - builtin module may
throw, 3rd party package may throw, runtime errors are all over the place.
Result type from @ts-rust/std package
aims to leverage this behavior by offering an API that is "throwless". As long as
you use the API provided by Result,
all possible errors will be handled gracefully and returned as
Err<E> variant. This is achieved by
using CheckedError<E> type.
Let's take a look at the example so you can see how it works in practice.
Say we have a function that returns a result with either a number or an error:
function getNumberResult(): Result<number, string> {
if (new Date().getDay() === 0) {
// if today is Sunday, we throw an error
// note: this is just an example! never do that in production code
throw new Error("no numbers on Sundays");
}
return Math.random() > 0.5 ? ok(2) : err("random error");
}
functions runResult, ok, err as well as type Result are imported from @ts-rust/std package.
In the example above, we throw an exception if today is Sunday. This is just an example
to illustrate how Result in this library works. In production code,
you should never throw exceptions from functions whose return type is Result<T, E>!
We can use now runResult function to execute the function and handle the result safely.
runResult will catch any errors thrown by the function and return an Err<E> variant if
an error occurs.
For functions that return raw values instead of Result, you can use run
function instead. run will return a Result<T, E> where T is the type of
the value returned by the function
const result: Result<number, string> = runResult(getNumberResult);
From this point on, we can safely work with the result variable.
If the result is Ok<number>, we can safely access the value.
if (result.isOk()) {
console.log(`Got number: ${result.value}`); // (property) value: number
return;
}
If the result is not Ok<number>, we can handle the error. Thanks to the type inference,
typescript knows that result is of type Err<string> (due to the way how isOk
and isErr are implemented).
const { error } = result; // const error: CheckedError<string>
if (error.isExpected()) {
// if error is expected, (e.g. `getNumberResult` returned `err(...)`) we can access the error value
console.log("Expected error of type string:", error.expected); // (property) expected: string
}
if (error.isUnexpected()) {
// the same way, if the error is unexpected (e.g. `getNumberResult` threw an exception),
// we can access the underlying error and its reason
console.log("Unexpected error:", error.unexpected); // (property) unexpected: ResultError
console.log("Error reason:", error.unexpected.reason); // (property) AnyError<ResultErrorKind>.reason: Error
}
Variants
Ok<T>- Ok value of typeT.Err<E>- Error value of typeCheckedError<E>.
Constructors
ok(value: T)- createsOk<T>variant.err(err: E)- createsErr<E>variant.
Methods
and
and<U>(x: Result<U, E>): Result<U, E>
Returns x if this result is Ok, otherwise returns the Err value of self.
const x = ok<number, string>(1);
const y = ok<number, string>(2);
const z = err<number, string>("failure");
expect(x.and(y)).toStrictEqual(ok(2));
expect(x.and(z)).toStrictEqual(err("failure"));
expect(z.and(x)).toStrictEqual(err("failure"));
andThen
andThen<U>(f: (x: T) => Result<U, E>): Result<U, E>
Applies f to the value if this result is Ok and returns its result,
otherwise returns the Err value of self.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.andThen((n) => ok(n * 2))).toStrictEqual(ok(4));
expect(y.andThen((n) => ok(n * 2))).toStrictEqual(err("failure"));
check
check(this: SettledResult<T, E>): this extends Ok<T, E> ? [true, T] : [false, CheckedError<E>]
Inspects the Result’s state, returning a tuple indicating success and either a value or an error.
- Only available on
Results that areSettled. - Returns
[true, T]if this is anOk, or[false, CheckedError<E>]if this is anErr. - Never throws, providing a safe way to access the result’s state without unwrapping.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.check()).toEqual([true, 42]);
expect(y.check()).toEqual([false, expect.objectContaining({ expected: "failure" })]);
clone
clone<U, F>(this: Result<Cloneable<U>, Cloneable<F>>): Result<U, F>
Returns a clone of the Result.
Only available on Results with Cloneable value and error.
class CloneableClass implements Clone<CloneableClass> {
constructor(public a: number) {}
clone(this: Clone<CloneableClass>): CloneableClass;
clone(this: CloneableClass): CloneableClass;
clone(): CloneableClass {
return new CloneableClass(this.a);
}
}
const cloneable = new CloneableClass(1);
const x = ok<CloneableClass, string>(cloneable);
const y = err<CloneableClass, string>("oops");
expect(x.clone()).not.toBe(x); // Different reference
expect(x.clone()).toStrictEqual(cloneable);
expect(y.clone().unwrapErr().expected).toBe("oops");
combine
combine<U extends Result<unknown, E>[]>(...results: U): Result<[T, ...OkValues<U>], E>
Combines this Result with other Result instances into a single
Result containing a tuple of values.
The combine method takes an arbitrary number of Result instances,
all sharing the same Err type. If all Result instances
(including this one) are Ok, it returns a Result with a tuple of
their Ok values in the order provided. If any Result is Err, it returns
that Err. The resulting tuple includes the value of this Result as the first
element, followed by the values from the provided Result instances.
const a = ok<Promise<number>, string>(Promise.resolve(1));
const b = ok<string, string>("hi");
const c = err<Date, string>("no");
const d = a.combine(b, c); // Result<[Promise<number>, string, Date], string>
copy
Returns a shallow copy of the Result.
const value = { a: 1 };
const x = ok<{ a: number }, string>(value);
expect(x.copy()).toStrictEqual(ok({ a: 1 }));
expect(x.copy()).not.toBe(x); // Different result reference
expect(x.copy().unwrap()).toBe(value); // Same value reference
err
err(this: SettledResult<T, E>): Option<E>
Converts this Result to an Option containing the error, if present.
Returns Some with the error value if this is an Err, or None if this is an Ok.
- Only available on
Results that areSettled. - Extracts the error from
CheckedErrorif it’s anExpectedError; returnsNoneforUnexpectedError.
const x = ok<number, string>(1);
const y = err<number, string>("failure");
expect(x.err()).toStrictEqual(none());
expect(y.err()).toStrictEqual(some("failure"));
expect
expect(this: SettledResult<T, E>, msg?: string): T
Retrieves the error if this result is an Err, or throws a ResultError
with an optional message if it’s an Ok.
Only available on Results that are Settled.
Throws ResultError if this result is an Ok.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.expectErr("Failed!")).toThrow(ResultError);
expect(isCheckedError(y.expectErr("Failed!"))).toBe(true);
expect(y.expectErr("Failed!").expected).toBe("failure");
expectErr
expectErr(this: SettledResult<T, E>, msg?: string): CheckedError<E>
Retrieves the error if this result is an Err, or throws a ResultError
with an optional message if it’s an Ok.
Only available on Results that are Settled.
Throws ResultError if this result is an Ok
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.expectErr("Failed!")).toThrow(ResultError);
expect(isCheckedError(y.expectErr("Failed!"))).toBe(true);
expect(y.expectErr("Failed!").expected).toBe("failure");
flatten
flatten<U, F>(this: Result<Result<U, F>, F>): Result<U, F>
Flattens a nested result (Result<Result<T, E>, E>) into a single result (Result<T, E>).
Only available on Results that hold another Result with the error of same type E.
const x: Result<Result<Result<number, string>, string>, string> = ok(ok(ok(6)));
const y: Result<Result<number, string>, string> = x.flatten();
const z: Result<Result<number, string>, string> = err("oops");
expect(x.flatten()).toStrictEqual(ok(ok(6)));
expect(y.flatten()).toStrictEqual(ok(6));
expect(z.flatten()).toStrictEqual(err("oops"));
inspect
inspect(f: (x: T) => unknown): Result<T, E>
Calls f with the value if this result is Ok, then returns a copy of this result.
- Returns a new
Resultinstance, not the original reference. - If
fthrows or returns aPromisethat rejects, the error is ignored.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
let sideEffect = 0;
expect(x.inspect((n) => (sideEffect = n))).toStrictEqual(ok(2));
expect(x.inspect((_) => { throw new Error(); })).toStrictEqual(ok(2));
expect(sideEffect).toBe(2);
expect(y.inspect((n) => (sideEffect = n))).toStrictEqual(err("failure"));
expect(sideEffect).toBe(2); // Unchanged
inspectErr
inspectErr(f: (x: CheckedError<E>) => unknown): Result<T, E>
Calls f with the error if this result is an Err, then returns a copy of this result.
- Returns a new
Resultinstance, not the original reference. - If
fthrows or returns aPromisethat rejects, the error is ignored.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
let sideEffect = 0;
expect(x.inspect((n) => (sideEffect = n))).toStrictEqual(ok(2));
expect(x.inspect((_) => { throw new Error(); })).toStrictEqual(ok(2));
expect(sideEffect).toBe(0); // unchanged
expect(y.inspect((n) => (sideEffect = n))).toStrictEqual(err("failure"));
expect(y.inspect((_) => { throw new Error(); })).toStrictEqual(err("failure"));
expect(sideEffect).toBe(2);
isErr
Checks if this result is an Err, narrowing its type to Err if true.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isErr()).toBe(false);
expect(y.isErr()).toBe(true);
isErrAnd
isErrAnd(f: (x: CheckedError<E>) => boolean): this is Err<T, E> & boolean
Returns true if the result is Err and f returns true for the contained error.
If f throws, false is returned.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isErrAnd((e) => e.expected === "failure")).toBe(false);
expect(y.isErrAnd((e) => e.expected === "failure")).toBe(true);
expect(y.isErrAnd((e) => Boolean(e.unexpected))).toBe(false);
isOk
Checks if this result is an Ok, narrowing its type to Ok if true.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isOk()).toBe(true);
expect(y.isOk()).toBe(false);
isOkAnd
isOkAnd(f: (x: T) => boolean): this is Ok<T, E> & boolean
Returns true if the result is Ok and f returns true for the contained value.
If f throws, false is returned.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.isOkAnd((n) => n > 0)).toBe(true);
expect(x.isOkAnd((n) => n < 0)).toBe(false);
expect(y.isOkAnd((_) => true)).toBe(false);
iter
iter(): IterableIterator<T, T, void>
Returns an iterator over this result’s value, yielding it if Ok or nothing if Err.
- Yields exactly one item for
Ok, or zero items forErr. - Compatible with
for...ofloops and spread operators. - Ignores the error value in
Errcases, focusing only on the success case.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
const iterX = x.iter();
expect(iterX.next()).toEqual({ value: 42, done: false });
expect(iterX.next()).toEqual({ done: true });
const iterY = y.iter();
expect(iterY.next()).toEqual({ done: true });
expect([...x.iter()]).toEqual([42]);
expect([...y.iter()]).toEqual([]);
map
map<U>(f: (x: T) => Awaited<U>): Result<U, E>
Transforms this result by applying f to the value if it’s an Ok,
or preserves the Err unchanged.
If f throws, returns an Err with an
UnexpectedError
containing the original error.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.map((n) => n * 2)).toStrictEqual(ok(4));
expect(x.map(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
expect(y.map((n) => n * 2)).toStrictEqual(err("failure"));
mapAll
mapAll<U, F>(f: (x: Result<T, E>) => Result<U, F>): Result<U, F>mapAll<U, F>(f: (x: Result<T, E>) => Promise<Result<U, F>>): PendingResult<Awaited<U>, Awaited<F>>
Maps this result by applying a callback f to its full state, executing the callback
for both Ok and Err, returning a new Result (or a PendingResult,
if provided callback returns a Promise).
Unlike andThen, which only invokes the callback for Ok,
this method always calls f, passing the entire Result as its argument.
If f throws or returns a Promise that rejects, an Err (or a PendingResult
that resolves to an Err) with an UnexpectedError is returned.
const okRes = ok<number, string>(42);
const errRes = err<number, string>("failure");
expect(okRes.mapAll((res) => ok(res.unwrapOr(0) + 1))).toStrictEqual(ok(43));
expect(errRes.mapAll((res) => ok(res.unwrapOr(0) + 1))).toStrictEqual(ok(1));
expect(okRes.mapAll((res) => (res.isOk() ? ok("success") : err("fail")))).toStrictEqual(ok("success"));
expect(errRes.mapAll(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
const mappedOk = okRes.mapAll((res) => Promise.resolve(ok(res.unwrapOr(0) + 1)));
expect(await mappedOk).toStrictEqual(ok(43));
const mappedErr = errRes.mapAll((res) => Promise.resolve(ok(res.unwrapOr(0) + 1)));
expect(await mappedErr).toStrictEqual(ok(1));
const mappedCheck = okRes.mapAll((res) => Promise.resolve(res.isOk() ? ok("success") : err("fail")));
expect(await mappedCheck).toStrictEqual(ok("success"));
const mappedThrow = errRes.mapAll(() => Promise.reject(new Error("boom")));
expect((await mappedThrow).unwrapErr().unexpected).toBeDefined();
mapErr
mapErr<F>(f: (e: E) => Awaited<F>): Result<T, F>
Transforms this result by applying f to the error if it’s an Err with
an expected error, or preserves the result unchanged.
- If
fthrows, returns anErrwith anUnexpectedErrorcontaining the original error. - If this is an
Errwith anUnexpectedError,fis not called, and the original error is preserved.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapErr((e) => e.length)).toStrictEqual(ok(2));
expect(y.mapErr((e) => e.length)).toStrictEqual(err(7));
expect(y.mapErr(() => { throw new Error("boom"); }).unwrapErr().unexpected).toBeDefined();
mapOr
mapOr<U>(this: SettledResult<T, E>, def: Awaited<U>, f: (x: T) => Awaited<U>): U
Returns f applied to the value if Ok, otherwise returns provided default.
- Only available on
Results that areSettled. fhas to return a synchronous (Awaited) value.- If
fthrows, returnsdef.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapOr(0, (n) => n * 2)).toBe(4);
expect(x.mapOr(0, () => { throw new Error("boom"); })).toBe(0);
expect(y.mapOr(0, (n) => n * 2)).toBe(0);
mapOrElse
mapOrElse<U>(this: SettledResult<T, E>, mkDef: () => Awaited<U>, f: (x: T) => Awaited<U>): U
Returns f applied to the contained value if Ok, otherwise returns the result of mkDef.
- Only available on
Results that areSettled. - If
fthrows, the error is silently ignored, and the result ofmkDefis returned.
If mkDef is called and throws an exception, ResultError is thrown
with the original error set as ResultError.reason.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.mapOrElse(() => 0, n => n * 2)).toBe(4);
expect(x.mapOrElse(() => 1, () => { throw new Error("boom") })).toBe(1);
expect(() => y.mapOrElse(() => { throw new Error("boom") }, n => n * 2)).toThrow(ResultError);
expect(y.mapOrElse(() => 0, n => n * 2)).toBe(0);
match
Matches this result, returning f applied to the value if Ok, or g applied
to the CheckedError if Err.
- Only available on
Results that areSettled. - If
forgreturn aPromisethat rejects, the caller is responsible for handling the rejection.
Throws ResultError if f or g throws an exception,
with the original error set as ResultError.reason.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.match(n => n * 2, () => 0)).toBe(4);
expect(() => x.match(_ => { throw new Error() }, () => 0)).toThrow(ResultError);
expect(y.match(n => n * 2, e => e.expected?.length)).toBe(7);
expect(() => y.match(n => n * 2, () => { throw new Error() })).toThrow(ResultError);
ok
Converts this result to an Option, discarding the error if present.
Maps Ok(_) to Some(_) and Err(_) to None.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.ok()).toStrictEqual(some(2));
expect(y.ok()).toStrictEqual(none());
or
or<F>(x: Result<T, F>): Result<T, F>
Returns the current result if it is Ok, otherwise returns x.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.or(ok(3))).toStrictEqual(ok(2));
expect(x.or(err("failure"))).toStrictEqual(ok(2));
expect(y.or(ok(3))).toStrictEqual(ok(3));
expect(y.or(err("another one"))).toStrictEqual(err("another one"));
orElse
orElse<F>(f: () => Result<T, F>): Result<T, F>
Returns the current result if Ok, otherwise returns the result of f.
If f throws, returns an Err with an
UnexpectedError
containing the original error.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.orElse(() => ok(3))).toStrictEqual(ok(2));
expect(y.orElse(() => ok(3))).toStrictEqual(ok(3));
expect(y.orElse(() => { throw new Error("boom") }).unwrapErr().unexpected).toBeDefined();
expect(y.orElse(() => err("another one"))).toStrictEqual(err("another one"));
tap
tap(f: (x: Result<T, E>) => unknown): Result<T, E>
Executes f with a copy of this result, then returns a new copy unchanged.
Useful for side-effects like logging, works with both Ok and Err.
- If
fthrows or rejects, the error is silently ignored. - If
freturns a promise, the promise is not awaited before returning.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
let log = "";
expect(x.tap(res => (log = res.toString()))).toStrictEqual(ok(42));
expect(log).toBe("Ok { 42 }");
expect(y.tap(res => (log = res.toString()))).toStrictEqual(err("failure"));
expect(log).toBe("Err { 'failure' }");
toPending
toPending(): PendingResult<Awaited<T>, Awaited<E>>
Converts this result to a PendingResult using
a shallow copy of its current state.
- Useful for transposing a result with a
PromiseLikevalue to aPendingResultwith anAwaitedvalue. - If inner
TorEis a promise-like that rejects, maps to aPendingResultthat resolves toErrwithUnexpectedError.
const value = { a: 1 };
const x = ok<{ a: number }, string>(value);
const pendingX = x.toPending();
expect(isPendingResult(pendingX)).toBe(true);
expect(await pendingX).toStrictEqual(ok({ a: 1 }));
value.a = 2;
expect(await pendingX).toStrictEqual(ok({ a: 2 }));
toPendingCloned
toPendingCloned(this: Result<Cloneable<T>, Cloneable<E>>): PendingResult<Awaited<T>, Awaited<E>>
Converts this result to a PendingResult using a deep clone of its current state.
- Useful for transposing a result with a
PromiseLikevalue to aPendingResultwith anAwaitedvalue, preserving independence from the original data. - If inner
TorEis a promise-like that rejects, maps to aPendingResultthat resolves toErrwithUnexpectedError.
class CloneableClass implements Clone<CloneableClass> {
constructor(public a: number) {}
clone(this: Clone<CloneableClass>): CloneableClass;
clone(this: CloneableClass): CloneableClass;
clone(): CloneableClass {
return new CloneableClass(this.a);
}
}
const value = new CloneableClass(0);
const x = ok<CloneableClass, number>(value);
const y = err<CloneableClass, number>(1);
const pendingX = x.toPendingCloned();
expect(isPendingResult(pendingX)).toBe(true);
expect((await pendingX).unwrap().a).toBe(0);
value.a = 42;
expect((await pendingX).unwrap().a).toBe(0);
expect(await y.toPendingCloned()).toStrictEqual(err(1));
toString
Generates a string representation of this result, reflecting its current state.
const x = ok<number, string>(2);
const y = err<number, string>("error");
expect(x.toString()).toBe("Ok { 2 }");
expect(y.toString()).toBe("Err { 'error' }");
transpose
transpose<U, F>(this: Result<Option<U>, F>): Option<Result<U, F>>
Transposes a Result of an Option into an Option of a Result.
Maps Ok(None) to None, Ok(Some(_)) to Some(Ok(_)) and Err(_) to Some(Err(_)).
const x = ok<Option<number>, string>(none());
const y = ok<Option<number>, string>(some(2));
const z = err<Option<number>, string>("error");
expect(x.transpose()).toStrictEqual(none());
expect(y.transpose()).toStrictEqual(some(ok(2)));
expect(z.transpose()).toStrictEqual(some(err("error")));
try
Extracts this result’s state, returning a tuple with a success flag, error, and value.
Inspired by the Try Operator proposal.
- Only available on
Results that areSettled. - Returns
[true, undefined, T]if this is anOk, or[false, CheckedError<E>, undefined]if this is anErr. - Never throws, offering a safe way to inspect the result’s state with explicit success indication.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.try()).toEqual([true, undefined, 42]);
expect(y.try()).toEqual([false, expect.objectContaining({ expected: "failure" }), undefined]);
unwrap
unwrap(this: SettledResult<T, E>): T
Retrieves the value if this result is an Ok, or throws a
ResultError if it’s an Err.
Only available on Results that are Settled.
Throws ResultError if this result is an Err.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(x.unwrap()).toBe(42);
expect(() => y.unwrap()).toThrow(ResultError);
unwrapErr
unwrapErr(this: SettledResult<T, E>): CheckedError<E>
Retrieves the CheckedError if this result is an Err, or
throws a ResultError if it’s an Ok.
Only available on Results that are Settled.
Throws ResultError if this result is an Ok.
const x = ok<number, string>(42);
const y = err<number, string>("failure");
expect(() => x.unwrapErr()).toThrow(ResultError);
expect(y.unwrapErr().expected).toBe("failure");
unwrapOr
unwrapOr(this: SettledResult<T, E>, def: Awaited<T>): T
Returns the contained value if Ok, or def if Err.
Only available on Results that are Settled.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.unwrapOr(0)).toBe(2);
expect(y.unwrapOr(0)).toBe(0);
unwrapOrElse
unwrapOrElse(this: SettledResult<T, E>, mkDef: () => Awaited<T>): T
Returns the contained value if Ok, or the result of mkDef if Err.
Only available on Results that are Settled.
Throws ResultError if mkDef throws, with the original error
set as ResultError.reason.
const x = ok<number, string>(2);
const y = err<number, string>("failure");
expect(x.unwrapOrElse(() => 0)).toBe(2);
expect(y.unwrapOrElse(() => 0)).toBe(0);
expect(() => y.unwrapOrElse(() => { throw new Error("boom") })).toThrow(ResultError);