Skip to content

Commit 6529d9e

Browse files
committed
feat(result): make AsyncResult.all short-circuit on first Fail
- Implement sequential evaluation with early exit to avoid waiting on later items when a failure is encountered\n- Add test verifying early return behavior (does not wait for slow item)\n- Document semantics in method JSDoc
1 parent 3751065 commit 6529d9e

File tree

2 files changed

+36
-1
lines changed

2 files changed

+36
-1
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { AsyncResult } from './async-result'
2+
3+
describe('AsyncResult.all (short-circuit)', () => {
4+
it('should short-circuit and not wait for later items after a failure', async () => {
5+
const a = AsyncResult.ok<number, string>(1)
6+
const bad = AsyncResult.fail<number, string>('boom')
7+
8+
const slow = AsyncResult.fromPromise<number, string>(
9+
new Promise<number>(resolve => setTimeout(() => resolve(99), 200))
10+
)
11+
12+
const start = Date.now()
13+
const res = await AsyncResult.all([a, bad, slow]).toPromise()
14+
const elapsed = Date.now() - start
15+
16+
expect(res.isFail()).toBe(true)
17+
expect(res.unwrapFail()).toBe('boom')
18+
// Should return well before the slow item resolves (200ms). Allow a generous margin.
19+
expect(elapsed).toBeLessThan(150)
20+
})
21+
})

src/result/async-result.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,22 @@ export class AsyncResult<TOk, TFail> {
3636
return new AsyncResult<TOk, TFail>(Result.fromPromise<TOk, TFail>(promise))
3737
}
3838

39+
/**
40+
* Aggregate a list of AsyncResult values, short-circuiting on the first Fail.
41+
* Note: evaluation is sequential to enable early exit; this method does not
42+
* guarantee concurrent execution of inputs. If all are Ok, returns Ok of the
43+
* collected values preserving order; otherwise returns the first Fail seen.
44+
*/
3945
static all<T, E>(items: ReadonlyArray<AsyncResult<T, E>>): AsyncResult<ReadonlyArray<T>, E> {
40-
const p = Promise.all(items.map(i => i.promise)).then(results => Result.sequence(results))
46+
const p = (async () => {
47+
const acc: T[] = []
48+
for (const ar of items) {
49+
const r = await ar.toPromise()
50+
if (r.isFail()) return Result.fail<ReadonlyArray<T>, E>(r.unwrapFail())
51+
acc.push(r.unwrap())
52+
}
53+
return Result.ok<ReadonlyArray<T>, E>(acc)
54+
})()
4155
return new AsyncResult<ReadonlyArray<T>, E>(p)
4256
}
4357

0 commit comments

Comments
 (0)