diff --git a/.changeset/strong-poems-thank.md b/.changeset/strong-poems-thank.md new file mode 100644 index 00000000..c8714422 --- /dev/null +++ b/.changeset/strong-poems-thank.md @@ -0,0 +1,5 @@ +--- +"neverthrow": minor +--- + +feat: fork() diff --git a/src/result-async.ts b/src/result-async.ts index 20120d2b..287c76dc 100644 --- a/src/result-async.ts +++ b/src/result-async.ts @@ -203,6 +203,13 @@ export class ResultAsync implements PromiseLike> { return this._promise.then((res) => res.match(ok, _err)) } + fork( + ok: (t: T) => Result | ResultAsync, + err: (e: E) => Result | ResultAsync, + ): ResultAsync { + return new ResultAsync(this._promise.then(async (res) => res.match(ok, err))) + } + unwrapOr(t: A): Promise { return this._promise.then((res) => res.unwrapOr(t)) } diff --git a/src/result.ts b/src/result.ts index ad447caa..a49b08d0 100644 --- a/src/result.ts +++ b/src/result.ts @@ -275,6 +275,25 @@ interface IResult { */ match(ok: (t: T) => A, err: (e: E) => B): A | B + /** + * Similar to `match` except the functions must return a new `Result`. + * + * @param ok + * @param err + */ + fork(ok: (t: T) => Result, err: (e: E) => Result): Result + + /** + * Similar to `fork` except the functions must return a new `ResultAsync`. + * + * @param ok + * @param err + */ + asyncFork( + ok: (t: T) => Result | ResultAsync, + err: (e: E) => Result | ResultAsync, + ): ResultAsync + /** * @deprecated will be removed in 9.0.0. * @@ -394,6 +413,17 @@ export class Ok implements IResult { return ok(this.value) } + fork(ok: (t: T) => Result, _err: (e: E) => Result): Result { + return ok(this.value) + } + + asyncFork( + ok: (t: T) => Result | ResultAsync, + _err: (e: E) => Result | ResultAsync, + ): ResultAsync { + return new ResultAsync(Promise.resolve(ok(this.value))) + } + safeUnwrap(): Generator, T> { const value = this.value /* eslint-disable-next-line require-yield */ @@ -493,6 +523,17 @@ export class Err implements IResult { return err(this.error) } + fork(_ok: (t: T) => Result, err: (e: E) => Result): Result { + return err(this.error) + } + + asyncFork( + _ok: (t: T) => Result | ResultAsync, + err: (e: E) => Result | ResultAsync, + ): ResultAsync { + return new ResultAsync(Promise.resolve(err(this.error))) + } + safeUnwrap(): Generator, T> { const error = this.error return (function* () { diff --git a/tests/index.test.ts b/tests/index.test.ts index 9f089a9d..8cb1d50c 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -288,6 +288,19 @@ describe('Result.Ok', () => { expect(errMapper).not.toHaveBeenCalled() }) + it('Forks on an Ok', () => { + const okMapper = vitest.fn((_val) => ok('weeeeee')) + const errMapper = vitest.fn((_val) => err('wooooo')) + + const forked = ok(12).fork(okMapper, errMapper) + + expect(forked.isOk()).toBe(true) + expect(forked).toBeInstanceOf(Ok) + expect(forked._unsafeUnwrap()).toBe('weeeeee') + expect(okMapper).toHaveBeenCalledTimes(1) + expect(errMapper).not.toHaveBeenCalled() + }) + it('Unwraps without issue', () => { const okVal = ok(12) @@ -450,6 +463,19 @@ describe('Result.Err', () => { expect(errMapper).toHaveBeenCalledTimes(1) }) + it('Forks on an Err', () => { + const okMapper = vitest.fn((_val) => ok('weeeeee')) + const errMapper = vitest.fn((_val) => err('wooooo')) + + const forked = err(12).fork(okMapper, errMapper) + + expect(forked.isErr()).toBe(true) + expect(forked).toBeInstanceOf(Err) + expect(forked._unsafeUnwrapErr()).toBe('wooooo') + expect(okMapper).not.toHaveBeenCalled() + expect(errMapper).toHaveBeenCalledTimes(1) + }) + it('Throws when you unwrap an Err', () => { const errVal = err('woopsies') @@ -1171,6 +1197,32 @@ describe('ResultAsync', () => { }) }) + describe('fork', () => { + it('Forks on an Ok', async () => { + const okMapper = vitest.fn((_val) => ok('weeeeee')) + const errMapper = vitest.fn((_val) => err('wooooo')) + + const forked = await okAsync(12).fork(okMapper, errMapper) + + expect(forked.isOk()).toBe(true) + expect(forked._unsafeUnwrap()).toBe('weeeeee') + expect(okMapper).toHaveBeenCalledTimes(1) + expect(errMapper).not.toHaveBeenCalled() + }) + + it('Forks on an Error', async () => { + const okMapper = vitest.fn((_val) => ok('weeeeee')) + const errMapper = vitest.fn((_val) => err('wooooo')) + + const forked = await errAsync('bad').fork(okMapper, errMapper) + + expect(forked.isErr()).toBe(true) + expect(forked._unsafeUnwrapErr()).toBe('wooooo') + expect(okMapper).not.toHaveBeenCalled() + expect(errMapper).toHaveBeenCalledTimes(1) + }) + }) + describe('unwrapOr', () => { it('returns a promise to the result value on an Ok', async () => { const unwrapped = await okAsync(12).unwrapOr(10)