Skip to content

Commit 6fa68e5

Browse files
committed
feat: fork()
1 parent 1d4cc19 commit 6fa68e5

File tree

3 files changed

+119
-0
lines changed

3 files changed

+119
-0
lines changed

src/result-async.ts

+7
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
203203
return this._promise.then((res) => res.match(ok, _err))
204204
}
205205

206+
fork<A, B, C, D>(
207+
ok: (t: T) => Result<A, B> | ResultAsync<A, B>,
208+
err: (e: E) => Result<C, D> | ResultAsync<C, D>,
209+
): ResultAsync<A | C, B | D> {
210+
return new ResultAsync(this._promise.then(async (res) => res.match(ok, err)))
211+
}
212+
206213
unwrapOr<A>(t: A): Promise<T | A> {
207214
return this._promise.then((res) => res.unwrapOr(t))
208215
}

src/result.ts

+41
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,25 @@ interface IResult<T, E> {
275275
*/
276276
match<A, B = A>(ok: (t: T) => A, err: (e: E) => B): A | B
277277

278+
/**
279+
* Similar to `match` except the functions must return a new `Result`.
280+
*
281+
* @param ok
282+
* @param err
283+
*/
284+
fork<A, B, C, D>(ok: (t: T) => Result<A, B>, err: (e: E) => Result<C, D>): Result<A | C, B | D>
285+
286+
/**
287+
* Similar to `fork` except the functions must return a new `ResultAsync`.
288+
*
289+
* @param ok
290+
* @param err
291+
*/
292+
asyncFork<A, B, C, D>(
293+
ok: (t: T) => Result<A, B> | ResultAsync<A, B>,
294+
err: (e: E) => Result<C, D> | ResultAsync<C, D>,
295+
): ResultAsync<A | C, B | D>
296+
278297
/**
279298
* @deprecated will be removed in 9.0.0.
280299
*
@@ -394,6 +413,17 @@ export class Ok<T, E> implements IResult<T, E> {
394413
return ok(this.value)
395414
}
396415

416+
fork<A, B, C, D>(ok: (t: T) => Result<A, B>, _err: (e: E) => Result<C, D>): Result<A | C, B | D> {
417+
return ok(this.value)
418+
}
419+
420+
asyncFork<A, B, C, D>(
421+
ok: (t: T) => Result<A, B> | ResultAsync<A, B>,
422+
_err: (e: E) => Result<C, D> | ResultAsync<C, D>,
423+
): ResultAsync<A | C, B | D> {
424+
return new ResultAsync(Promise.resolve(ok(this.value)))
425+
}
426+
397427
safeUnwrap(): Generator<Err<never, E>, T> {
398428
const value = this.value
399429
/* eslint-disable-next-line require-yield */
@@ -493,6 +523,17 @@ export class Err<T, E> implements IResult<T, E> {
493523
return err(this.error)
494524
}
495525

526+
fork<A, B, C, D>(_ok: (t: T) => Result<A, B>, err: (e: E) => Result<C, D>): Result<A | C, B | D> {
527+
return err(this.error)
528+
}
529+
530+
asyncFork<A, B, C, D>(
531+
_ok: (t: T) => Result<A, B> | ResultAsync<A, B>,
532+
err: (e: E) => Result<C, D> | ResultAsync<C, D>,
533+
): ResultAsync<A | C, B | D> {
534+
return new ResultAsync(Promise.resolve(err(this.error)))
535+
}
536+
496537
safeUnwrap(): Generator<Err<never, E>, T> {
497538
const error = this.error
498539
return (function* () {

tests/index.test.ts

+71
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ import {
1717

1818
import { vitest, describe, expect, it } from 'vitest'
1919

20+
const x = ok<number, string>(123).fork(
21+
(x) => ok(x + 321),
22+
(y) => ok('y + 444'),
23+
)
24+
25+
ok<number, string>(123)
26+
.asyncFork(
27+
(x) => okAsync(x + 321),
28+
(y) => okAsync('y + 444'),
29+
)
30+
.map((x) => x - 321)
31+
32+
okAsync<number, string>(123)
33+
.fork(
34+
(x) => okAsync(x + 321),
35+
(y) => okAsync('y + 444'),
36+
)
37+
.map((x) => x - 321)
38+
2039
describe('Result.Ok', () => {
2140
it('Creates an Ok value', () => {
2241
const okVal = ok(12)
@@ -288,6 +307,19 @@ describe('Result.Ok', () => {
288307
expect(errMapper).not.toHaveBeenCalled()
289308
})
290309

310+
it('Forks on an Ok', () => {
311+
const okMapper = vitest.fn((_val) => ok('weeeeee'))
312+
const errMapper = vitest.fn((_val) => err('wooooo'))
313+
314+
const forked = ok(12).fork(okMapper, errMapper)
315+
316+
expect(forked.isOk()).toBe(true)
317+
expect(forked).toBeInstanceOf(Ok)
318+
expect(forked._unsafeUnwrap()).toBe('weeeeee')
319+
expect(okMapper).toHaveBeenCalledTimes(1)
320+
expect(errMapper).not.toHaveBeenCalled()
321+
})
322+
291323
it('Unwraps without issue', () => {
292324
const okVal = ok(12)
293325

@@ -450,6 +482,19 @@ describe('Result.Err', () => {
450482
expect(errMapper).toHaveBeenCalledTimes(1)
451483
})
452484

485+
it('Forks on an Err', () => {
486+
const okMapper = vitest.fn((_val) => ok('weeeeee'))
487+
const errMapper = vitest.fn((_val) => err('wooooo'))
488+
489+
const forked = err(12).fork(okMapper, errMapper)
490+
491+
expect(forked.isErr()).toBe(true)
492+
expect(forked).toBeInstanceOf(Err)
493+
expect(forked._unsafeUnwrapErr()).toBe('wooooo')
494+
expect(okMapper).not.toHaveBeenCalled()
495+
expect(errMapper).toHaveBeenCalledTimes(1)
496+
})
497+
453498
it('Throws when you unwrap an Err', () => {
454499
const errVal = err('woopsies')
455500

@@ -1171,6 +1216,32 @@ describe('ResultAsync', () => {
11711216
})
11721217
})
11731218

1219+
describe('fork', () => {
1220+
it('Forks on an Ok', async () => {
1221+
const okMapper = vitest.fn((_val) => ok('weeeeee'))
1222+
const errMapper = vitest.fn((_val) => err('wooooo'))
1223+
1224+
const forked = await okAsync(12).fork(okMapper, errMapper)
1225+
1226+
expect(forked.isOk()).toBe(true)
1227+
expect(forked._unsafeUnwrap()).toBe('weeeeee')
1228+
expect(okMapper).toHaveBeenCalledTimes(1)
1229+
expect(errMapper).not.toHaveBeenCalled()
1230+
})
1231+
1232+
it('Forks on an Error', async () => {
1233+
const okMapper = vitest.fn((_val) => ok('weeeeee'))
1234+
const errMapper = vitest.fn((_val) => err('wooooo'))
1235+
1236+
const forked = await errAsync('bad').fork(okMapper, errMapper)
1237+
1238+
expect(forked.isErr()).toBe(true)
1239+
expect(forked._unsafeUnwrapErr()).toBe('wooooo')
1240+
expect(okMapper).not.toHaveBeenCalled()
1241+
expect(errMapper).toHaveBeenCalledTimes(1)
1242+
})
1243+
})
1244+
11741245
describe('unwrapOr', () => {
11751246
it('returns a promise to the result value on an Ok', async () => {
11761247
const unwrapped = await okAsync(12).unwrapOr(10)

0 commit comments

Comments
 (0)