← All tasksTypeScript · Middle

Result<T, E>: errors without exceptions

The Result<T, E> type is either a success { ok: true, value } or a failure { ok: false, error }. Write precisely typed helpers for it.

ok(value) and err(error) create a result, mapResult(result, fn) transforms a success value and passes a failure through unchanged (without calling fn), and unwrapOr(result, fallback) returns the value or the fallback. The error type should carry over to the result of mapResult.

Tests (8)

Type tests are checked by the TypeScript compiler in strict mode. Expect<Equal<A, B>> passes when the types match exactly, and // @ts-expect-error passes when the next line does not compile. The first run downloads the compiler, so it takes a few seconds.

  1. types
    const r = ok(42);
    type _ = Expect<Equal<typeof r, Result<number, never>>>;
  2. types
    const e = err('not found');
    type _ = Expect<Equal<typeof e, Result<never, string>>>;
  3. types
    const r = {} as Result<number, string>;
    const m = mapResult(r, (n) => String(n));
    type _ = Expect<Equal<typeof m, Result<string, string>>>;
  4. types
    const r = {} as Result<number, string>;
    const v = unwrapOr(r, 0);
    type _ = Expect<Equal<typeof v, number>>;
  5. expect(ok(1)).toEqual({ ok: true, value: 1 });
    expect(err('x')).toEqual({ ok: false, error: 'x' });
  6. expect(mapResult(ok(2), (n) => n * 10)).toEqual({ ok: true, value: 20 });
  7. const f = fn((n) => n * 10);
    const failure = err('bad');
    expect(mapResult(failure, f)).toEqual({ ok: false, error: 'bad' });
    expect(f).toHaveBeenCalledTimes(0);
  8. expect(unwrapOr(ok(5), 0)).toBe(5);
    expect(unwrapOr(err('bad'), 0)).toBe(0);