tcdona_unilib 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,858 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { Eff, EffContext } from './eff';
3
+ import { err, ok, Result, ResultAsync } from 'neverthrow';
4
+
5
+ describe('eff', () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ vi.useRealTimers();
13
+ });
14
+
15
+ // ============================================================
16
+ // Eff.root
17
+ // ============================================================
18
+ describe('eff.root', () => {
19
+ test('基础用法:返回成功结果', async () => {
20
+ const result = await Eff.root<string, Error>(async () => {
21
+ return ok('hello');
22
+ });
23
+
24
+ expect(result.isOk()).toBe(true);
25
+ expect(result._unsafeUnwrap()).toBe('hello');
26
+ });
27
+
28
+ test('基础用法:返回错误结果', async () => {
29
+ const result = await Eff.root<string, Error>(async () => {
30
+ return err(new Error('something went wrong'));
31
+ });
32
+
33
+ expect(result.isErr()).toBe(true);
34
+ expect(result._unsafeUnwrapErr().message).toBe('something went wrong');
35
+ });
36
+
37
+ test('异常捕获:executor 抛出异常会被转为 err', async () => {
38
+ const result = await Eff.root<string, Error>(async () => {
39
+ throw new Error('unexpected error');
40
+ });
41
+
42
+ expect(result.isErr()).toBe(true);
43
+ expect(result._unsafeUnwrapErr().message).toBe('unexpected error');
44
+ });
45
+
46
+ test('async executor', async () => {
47
+ const result = await Eff.root<number, string>(async () => {
48
+ return ok(42);
49
+ });
50
+
51
+ expect(result._unsafeUnwrap()).toBe(42);
52
+ });
53
+
54
+ test('提供 signal 用于取消检查', async () => {
55
+ let receivedSignal: AbortSignal | null = null;
56
+
57
+ await Eff.root<void, never>(async ({ signal }) => {
58
+ receivedSignal = signal;
59
+ return ok(undefined);
60
+ });
61
+
62
+ expect(receivedSignal).toBeInstanceOf(AbortSignal);
63
+ });
64
+ });
65
+
66
+ // ============================================================
67
+ // Eff.create
68
+ // ============================================================
69
+ describe('eff.create', () => {
70
+ test('基础用法:在父任务中创建子任务', async () => {
71
+ const result = await Eff.root<number, Error>(async ({ signal }) => {
72
+ // 创建子任务
73
+ const childResult = await Eff.create<number, Error>(
74
+ async () => ok(100),
75
+ signal,
76
+ );
77
+ return childResult;
78
+ });
79
+
80
+ expect(result._unsafeUnwrap()).toBe(100);
81
+ });
82
+
83
+ test('父 signal abort 时子任务能感知', async () => {
84
+ vi.useRealTimers();
85
+ const controller = new AbortController();
86
+ let childSignalAborted = false;
87
+ let listenerSetup = false;
88
+
89
+ // 启动子任务
90
+ const childPromise = Eff.create<string, Error>(async ({ signal }) => {
91
+ // 监听 abort
92
+ signal.addEventListener('abort', () => {
93
+ childSignalAborted = true;
94
+ });
95
+ listenerSetup = true;
96
+
97
+ // 等待一段时间让外部有机会 abort
98
+ await delay(100);
99
+
100
+ return signal.aborted ? ok('aborted') : ok('done');
101
+ }, controller.signal);
102
+
103
+ // 等待监听器设置完成
104
+ await vi.waitFor(() => {
105
+ if (!listenerSetup) throw new Error('waiting');
106
+ });
107
+
108
+ // abort 父 signal
109
+ controller.abort();
110
+
111
+ // 等待子任务
112
+ await childPromise;
113
+
114
+ expect(childSignalAborted).toBe(true);
115
+ vi.useFakeTimers();
116
+ });
117
+
118
+ test('响应式取消示例:fetch 请求', async () => {
119
+ vi.useRealTimers();
120
+
121
+ const mockFetch = vi.fn().mockImplementation(async (_url, options) => {
122
+ // 检查是否被 abort
123
+ if (options?.signal?.aborted) {
124
+ throw new DOMException('Aborted', 'AbortError');
125
+ }
126
+ return { ok: true, json: () => Promise.resolve({ data: 'test' }) };
127
+ });
128
+ globalThis.fetch = mockFetch;
129
+
130
+ const controller = new AbortController();
131
+
132
+ const result = await Eff.create<{ data: string }, Error>(
133
+ async ({ signal }) => {
134
+ try {
135
+ const response = await fetch('/api/data', { signal });
136
+ const data = await response.json();
137
+ return ok(data);
138
+ } catch (e) {
139
+ return err(e as Error);
140
+ }
141
+ },
142
+ controller.signal,
143
+ );
144
+
145
+ expect(result._unsafeUnwrap()).toEqual({ data: 'test' });
146
+ expect(mockFetch).toHaveBeenCalledWith('/api/data', {
147
+ signal: expect.any(AbortSignal),
148
+ });
149
+
150
+ vi.useFakeTimers();
151
+ });
152
+ });
153
+
154
+ // ============================================================
155
+ // Eff.all
156
+ // ============================================================
157
+ describe('eff.all', () => {
158
+ test('全部成功:返回所有结果数组', async () => {
159
+ const controller = new AbortController();
160
+
161
+ const result = await Eff.all<number, Error>(
162
+ [async () => ok(1), async () => ok(2), async () => ok(3)],
163
+ controller.signal,
164
+ );
165
+
166
+ expect(result._unsafeUnwrap()).toEqual([1, 2, 3]);
167
+ });
168
+
169
+ test('结果顺序与 executor 顺序一致(即使完成顺序不同)', async () => {
170
+ vi.useRealTimers();
171
+ const controller = new AbortController();
172
+
173
+ const result = await Eff.all<string, Error>(
174
+ [
175
+ async () => {
176
+ await delay(30);
177
+ return ok('slow');
178
+ },
179
+ async () => {
180
+ await delay(10);
181
+ return ok('fast');
182
+ },
183
+ async () => {
184
+ await delay(20);
185
+ return ok('medium');
186
+ },
187
+ ],
188
+ controller.signal,
189
+ );
190
+
191
+ // 顺序应该是 executor 顺序,不是完成顺序
192
+ expect(result._unsafeUnwrap()).toEqual(['slow', 'fast', 'medium']);
193
+
194
+ vi.useFakeTimers();
195
+ });
196
+
197
+ test('任一失败:立即返回错误并 abort 其他', async () => {
198
+ vi.useRealTimers();
199
+ const controller = new AbortController();
200
+ const abortedTasks: number[] = [];
201
+
202
+ const result = await Eff.all<number, string>(
203
+ [
204
+ async ({ signal }) => {
205
+ signal.addEventListener('abort', () => abortedTasks.push(1));
206
+ await delay(100);
207
+ return ok(1);
208
+ },
209
+ async () => {
210
+ await delay(10);
211
+ return err('task 2 failed');
212
+ },
213
+ async ({ signal }) => {
214
+ signal.addEventListener('abort', () => abortedTasks.push(3));
215
+ await delay(100);
216
+ return ok(3);
217
+ },
218
+ ],
219
+ controller.signal,
220
+ );
221
+
222
+ expect(result.isErr()).toBe(true);
223
+ expect(result._unsafeUnwrapErr()).toBe('task 2 failed');
224
+ // 其他任务应该收到 abort 信号
225
+ expect(abortedTasks).toContain(1);
226
+ expect(abortedTasks).toContain(3);
227
+
228
+ vi.useFakeTimers();
229
+ });
230
+
231
+ test('实际应用:并行获取多个资源', async () => {
232
+ const controller = new AbortController();
233
+
234
+ // 模拟并行获取用户信息和订单信息
235
+ const result = await Eff.all<
236
+ { type: string; data: string },
237
+ { code: number; message: string }
238
+ >(
239
+ [
240
+ async () => ok({ type: 'user', data: 'John' }),
241
+ async () => ok({ type: 'orders', data: '3 items' }),
242
+ async () => ok({ type: 'notifications', data: '5 unread' }),
243
+ ],
244
+ controller.signal,
245
+ );
246
+
247
+ const [user, orders, notifications] = result._unsafeUnwrap();
248
+ expect(user.data).toBe('John');
249
+ expect(orders.data).toBe('3 items');
250
+ expect(notifications.data).toBe('5 unread');
251
+ });
252
+ });
253
+
254
+ // ============================================================
255
+ // Eff.any
256
+ // ============================================================
257
+ describe('eff.any', () => {
258
+ test('任一成功:返回第一个成功的结果', async () => {
259
+ vi.useRealTimers();
260
+ const controller = new AbortController();
261
+
262
+ const result = await Eff.any<string, Error>(
263
+ [
264
+ async () => {
265
+ await delay(50);
266
+ return ok('slow winner');
267
+ },
268
+ async () => {
269
+ await delay(10);
270
+ return ok('fast winner');
271
+ },
272
+ async () => err(new Error('loser')),
273
+ ],
274
+ controller.signal,
275
+ );
276
+
277
+ expect(result._unsafeUnwrap()).toBe('fast winner');
278
+
279
+ vi.useFakeTimers();
280
+ });
281
+
282
+ test('成功后 abort 其他任务', async () => {
283
+ vi.useRealTimers();
284
+ const controller = new AbortController();
285
+ let slowTaskAborted = false;
286
+
287
+ const result = await Eff.any<string, Error>(
288
+ [
289
+ async ({ signal }) => {
290
+ signal.addEventListener('abort', () => {
291
+ slowTaskAborted = true;
292
+ });
293
+ await delay(100);
294
+ return ok('slow');
295
+ },
296
+ async () => ok('fast'),
297
+ ],
298
+ controller.signal,
299
+ );
300
+
301
+ expect(result._unsafeUnwrap()).toBe('fast');
302
+ expect(slowTaskAborted).toBe(true);
303
+
304
+ vi.useFakeTimers();
305
+ });
306
+
307
+ test('全部失败:返回所有错误数组', async () => {
308
+ const controller = new AbortController();
309
+
310
+ const result = await Eff.any<string, string>(
311
+ [async () => err('error 1'), async () => err('error 2'), async () => err('error 3')],
312
+ controller.signal,
313
+ );
314
+
315
+ expect(result.isErr()).toBe(true);
316
+ expect(result._unsafeUnwrapErr()).toEqual(['error 1', 'error 2', 'error 3']);
317
+ });
318
+
319
+ test('实际应用:多源数据获取(任一成功即可)', async () => {
320
+ vi.useRealTimers();
321
+ const controller = new AbortController();
322
+
323
+ // 模拟从多个 CDN 获取资源,任一成功即可
324
+ const result = await Eff.any<string, string>(
325
+ [
326
+ async () => {
327
+ await delay(100);
328
+ return ok('cdn1-data');
329
+ },
330
+ async () => {
331
+ await delay(10); // 最快
332
+ return ok('cdn2-data');
333
+ },
334
+ async () => err('cdn3-unavailable'),
335
+ ],
336
+ controller.signal,
337
+ );
338
+
339
+ expect(result._unsafeUnwrap()).toBe('cdn2-data');
340
+
341
+ vi.useFakeTimers();
342
+ });
343
+ });
344
+
345
+ // ============================================================
346
+ // Eff.race
347
+ // ============================================================
348
+ describe('eff.race', () => {
349
+ test('第一个完成的是成功', async () => {
350
+ vi.useRealTimers();
351
+ const controller = new AbortController();
352
+
353
+ const result = await Eff.race<number, Error>(
354
+ [
355
+ async () => {
356
+ await delay(10);
357
+ return ok(1);
358
+ },
359
+ async () => {
360
+ await delay(50);
361
+ return ok(2);
362
+ },
363
+ ],
364
+ controller.signal,
365
+ );
366
+
367
+ expect(result._unsafeUnwrap()).toBe(1);
368
+
369
+ vi.useFakeTimers();
370
+ });
371
+
372
+ test('第一个完成的是失败', async () => {
373
+ vi.useRealTimers();
374
+ const controller = new AbortController();
375
+
376
+ const result = await Eff.race<number, string>(
377
+ [
378
+ async () => {
379
+ await delay(10);
380
+ return err('fast error');
381
+ },
382
+ async () => {
383
+ await delay(50);
384
+ return ok(2);
385
+ },
386
+ ],
387
+ controller.signal,
388
+ );
389
+
390
+ expect(result.isErr()).toBe(true);
391
+ expect(result._unsafeUnwrapErr()).toBe('fast error');
392
+
393
+ vi.useFakeTimers();
394
+ });
395
+
396
+ test('实际应用:超时控制', async () => {
397
+ vi.useRealTimers();
398
+ const controller = new AbortController();
399
+
400
+ const result = await Eff.race<string, string>(
401
+ [
402
+ // 实际任务
403
+ async () => {
404
+ await delay(100);
405
+ return ok('task completed');
406
+ },
407
+ // 超时控制
408
+ async () => {
409
+ await delay(50);
410
+ return err('timeout');
411
+ },
412
+ ],
413
+ controller.signal,
414
+ );
415
+
416
+ expect(result.isErr()).toBe(true);
417
+ expect(result._unsafeUnwrapErr()).toBe('timeout');
418
+
419
+ vi.useFakeTimers();
420
+ });
421
+ });
422
+
423
+ // ============================================================
424
+ // Eff.allSettled
425
+ // ============================================================
426
+ describe('eff.allSettled', () => {
427
+ test('收集所有结果(无论成功失败)', async () => {
428
+ const controller = new AbortController();
429
+
430
+ const result = await Eff.allSettled<number, string>(
431
+ [async () => ok(1), async () => err('failed'), async () => ok(3)],
432
+ controller.signal,
433
+ );
434
+
435
+ const results = result._unsafeUnwrap();
436
+ expect(results).toHaveLength(3);
437
+ expect(results[0]._unsafeUnwrap()).toBe(1);
438
+ expect(results[1].isErr()).toBe(true);
439
+ expect(results[1]._unsafeUnwrapErr()).toBe('failed');
440
+ expect(results[2]._unsafeUnwrap()).toBe(3);
441
+ });
442
+
443
+ test('不会因为某个失败而 abort 其他', async () => {
444
+ vi.useRealTimers();
445
+ const controller = new AbortController();
446
+ const taskExecuted = [false, false, false];
447
+
448
+ const result = await Eff.allSettled<number, string>(
449
+ [
450
+ async () => {
451
+ await delay(10);
452
+ taskExecuted[0] = true;
453
+ return ok(1);
454
+ },
455
+ async () => {
456
+ taskExecuted[1] = true;
457
+ return err('failed early');
458
+ },
459
+ async () => {
460
+ await delay(20);
461
+ taskExecuted[2] = true;
462
+ return ok(3);
463
+ },
464
+ ],
465
+ controller.signal,
466
+ );
467
+
468
+ expect(result._unsafeUnwrap()).toHaveLength(3);
469
+ expect(taskExecuted).toEqual([true, true, true]);
470
+
471
+ vi.useFakeTimers();
472
+ });
473
+
474
+ test('实际应用:批量操作并报告结果', async () => {
475
+ const controller = new AbortController();
476
+
477
+ // 模拟批量发送邮件
478
+ const result = await Eff.allSettled<string, string>(
479
+ [
480
+ async () => ok('email1@test.com: sent'),
481
+ async () => err('email2@test.com: invalid address'),
482
+ async () => ok('email3@test.com: sent'),
483
+ ],
484
+ controller.signal,
485
+ );
486
+
487
+ const results = result._unsafeUnwrap();
488
+ const successes = results.filter((r) => r.isOk()).length;
489
+ const failures = results.filter((r) => r.isErr()).length;
490
+
491
+ expect(successes).toBe(2);
492
+ expect(failures).toBe(1);
493
+ });
494
+ });
495
+
496
+ // ============================================================
497
+ // Signal 传播
498
+ // ============================================================
499
+ describe('signal 传播', () => {
500
+ test('父 abort 自动传播到子', async () => {
501
+ vi.useRealTimers();
502
+ const controller = new AbortController();
503
+ let childAborted = false;
504
+ let listenerSetup = false;
505
+
506
+ const taskPromise = Eff.create<string, Error>(async ({ signal }) => {
507
+ signal.addEventListener('abort', () => {
508
+ childAborted = true;
509
+ });
510
+ listenerSetup = true;
511
+
512
+ await delay(100);
513
+ return signal.aborted ? ok('aborted') : ok('done');
514
+ }, controller.signal);
515
+
516
+ // 等待监听器设置完成
517
+ await vi.waitFor(() => {
518
+ if (!listenerSetup) throw new Error('waiting');
519
+ });
520
+
521
+ // abort 父
522
+ controller.abort();
523
+
524
+ await taskPromise;
525
+
526
+ expect(childAborted).toBe(true);
527
+ vi.useFakeTimers();
528
+ });
529
+
530
+ test('提前 abort 时返回 ok([])', async () => {
531
+ const controller = new AbortController();
532
+ controller.abort(); // 提前 abort
533
+
534
+ const result = await Eff.all<number, Error>(
535
+ [async () => ok(1), async () => ok(2)],
536
+ controller.signal,
537
+ );
538
+
539
+ // 取消是流程控制,不是失败
540
+ expect(result.isOk()).toBe(true);
541
+ expect(result._unsafeUnwrap()).toEqual([]);
542
+ });
543
+
544
+ test('executor 应主动检查 signal.aborted 并提前返回', async () => {
545
+ vi.useRealTimers();
546
+ const controller = new AbortController();
547
+ let iterationCount = 0;
548
+
549
+ const taskPromise = Eff.create<number, Error>(async ({ signal }) => {
550
+ // 模拟长时间循环任务
551
+ for (let i = 0; i < 100; i++) {
552
+ // 关键:检查 abort 状态
553
+ if (signal.aborted) {
554
+ return ok(iterationCount); // 提前返回当前进度
555
+ }
556
+ iterationCount++;
557
+ await delay(10);
558
+ }
559
+ return ok(iterationCount);
560
+ }, controller.signal);
561
+
562
+ // 让任务运行一段时间
563
+ await delay(50);
564
+ controller.abort();
565
+
566
+ const result = await taskPromise;
567
+ // 应该在某个中间点停止
568
+ expect(result._unsafeUnwrap()).toBeLessThan(100);
569
+ expect(result._unsafeUnwrap()).toBeGreaterThan(0);
570
+
571
+ vi.useFakeTimers();
572
+ });
573
+ });
574
+
575
+ // ============================================================
576
+ // 嵌套任务
577
+ // ============================================================
578
+ describe('嵌套任务', () => {
579
+ test('多层嵌套:祖父 → 父 → 子', async () => {
580
+ const result = await Eff.root<string, Error>(async ({ signal }) => {
581
+ const parentResult = await Eff.create<string, Error>(
582
+ async ({ signal: parentSignal }) => {
583
+ const childResult = await Eff.create<string, Error>(
584
+ async () => ok('deep value'),
585
+ parentSignal,
586
+ );
587
+ return childResult.map((v) => `parent(${v})`);
588
+ },
589
+ signal,
590
+ );
591
+ return parentResult.map((v) => `root(${v})`);
592
+ });
593
+
594
+ expect(result._unsafeUnwrap()).toBe('root(parent(deep value))');
595
+ });
596
+
597
+ test('嵌套的 all 调用', async () => {
598
+ const controller = new AbortController();
599
+
600
+ const result = await Eff.all<number[], Error>(
601
+ [
602
+ ({ signal }) =>
603
+ Eff.all<number, Error>(
604
+ [async () => ok(1), async () => ok(2)],
605
+ signal,
606
+ ),
607
+ ({ signal }) =>
608
+ Eff.all<number, Error>(
609
+ [async () => ok(3), async () => ok(4)],
610
+ signal,
611
+ ),
612
+ ],
613
+ controller.signal,
614
+ );
615
+
616
+ expect(result._unsafeUnwrap()).toEqual([
617
+ [1, 2],
618
+ [3, 4],
619
+ ]);
620
+ });
621
+ });
622
+
623
+ // ============================================================
624
+ // 错误处理
625
+ // ============================================================
626
+ describe('错误处理模式', () => {
627
+ test('使用 Result 的 map/mapErr 链式处理', async () => {
628
+ const result = await Eff.root<number, { code: string; message: string }>(
629
+ async () => {
630
+ return err({ code: 'NOT_FOUND', message: 'Resource not found' });
631
+ },
632
+ );
633
+
634
+ const handled = result
635
+ .map((v) => v * 2) // 不会执行
636
+ .mapErr((e) => `Error ${e.code}: ${e.message}`);
637
+
638
+ expect(handled._unsafeUnwrapErr()).toBe('Error NOT_FOUND: Resource not found');
639
+ });
640
+
641
+ test('使用 match 进行分支处理', async () => {
642
+ const result = await Eff.root<number, string>(async () => {
643
+ return ok(42);
644
+ });
645
+
646
+ const message = result.match(
647
+ (value) => `Success: ${value}`,
648
+ (error) => `Failed: ${error}`,
649
+ );
650
+
651
+ expect(message).toBe('Success: 42');
652
+ });
653
+
654
+ test('使用 andThen 进行链式异步操作', async () => {
655
+ const result = await Eff.root<number, string>(async () => {
656
+ return ok(10);
657
+ });
658
+
659
+ const finalResult = await result.asyncAndThen((value) => {
660
+ // 可以在这里做进一步的异步处理
661
+ return new ResultAsync(Promise.resolve(ok(value * 2)));
662
+ });
663
+
664
+ expect(finalResult._unsafeUnwrap()).toBe(20);
665
+ });
666
+ });
667
+
668
+ // ============================================================
669
+ // 实际应用场景
670
+ // ============================================================
671
+ describe('实际应用', () => {
672
+ test('场景:带重试的 API 调用', async () => {
673
+ vi.useRealTimers();
674
+ const controller = new AbortController();
675
+ let attempts = 0;
676
+
677
+ const fetchWithRetry = async (
678
+ maxRetries: number,
679
+ signal: AbortSignal,
680
+ ): Promise<Result<string, string>> => {
681
+ for (let i = 0; i < maxRetries; i++) {
682
+ if (signal.aborted) {
683
+ return err('cancelled');
684
+ }
685
+
686
+ attempts++;
687
+ // 模拟前两次失败,第三次成功
688
+ if (attempts >= 3) {
689
+ return ok(`success on attempt ${attempts}`);
690
+ }
691
+
692
+ await delay(10); // 重试间隔
693
+ }
694
+ return err('max retries exceeded');
695
+ };
696
+
697
+ const result = await Eff.create<string, string>(
698
+ ({ signal }) => fetchWithRetry(5, signal),
699
+ controller.signal,
700
+ );
701
+
702
+ expect(result._unsafeUnwrap()).toBe('success on attempt 3');
703
+
704
+ vi.useFakeTimers();
705
+ });
706
+
707
+ test('场景:并行请求 + 超时控制', async () => {
708
+ vi.useRealTimers();
709
+ const controller = new AbortController();
710
+
711
+ // 使用 race 实现超时
712
+ const withTimeout = <T, E>(
713
+ executor: (ctx: EffContext) => Promise<Result<T, E>>,
714
+ timeoutMs: number,
715
+ signal: AbortSignal,
716
+ ) =>
717
+ Eff.race<T, E | 'timeout'>(
718
+ [
719
+ executor,
720
+ async () => {
721
+ await delay(timeoutMs);
722
+ return err('timeout' as const);
723
+ },
724
+ ],
725
+ signal,
726
+ );
727
+
728
+ const result = await withTimeout(
729
+ async () => {
730
+ await delay(10);
731
+ return ok('fast response');
732
+ },
733
+ 100,
734
+ controller.signal,
735
+ );
736
+
737
+ expect(result._unsafeUnwrap()).toBe('fast response');
738
+
739
+ vi.useFakeTimers();
740
+ });
741
+
742
+ test('场景:并发限制(简化版)', async () => {
743
+ vi.useRealTimers();
744
+ const controller = new AbortController();
745
+ const runningTasks = new Set<number>();
746
+ let maxConcurrent = 0;
747
+
748
+ const tasks = Array.from({ length: 6 }, (_, i) => async () => {
749
+ runningTasks.add(i);
750
+ maxConcurrent = Math.max(maxConcurrent, runningTasks.size);
751
+ await delay(20);
752
+ runningTasks.delete(i);
753
+ return ok(i);
754
+ });
755
+
756
+ // 分批执行,每批 2 个
757
+ const batchSize = 2;
758
+ const results: number[] = [];
759
+
760
+ for (let i = 0; i < tasks.length; i += batchSize) {
761
+ const batch = tasks.slice(i, i + batchSize) as [
762
+ typeof tasks[0],
763
+ ...typeof tasks,
764
+ ];
765
+ const batchResult = await Eff.all<number, never>(
766
+ batch,
767
+ controller.signal,
768
+ );
769
+ results.push(...batchResult._unsafeUnwrap());
770
+ }
771
+
772
+ expect(results).toEqual([0, 1, 2, 3, 4, 5]);
773
+ expect(maxConcurrent).toBeLessThanOrEqual(2);
774
+
775
+ vi.useFakeTimers();
776
+ });
777
+
778
+ test('场景:资源获取与清理', async () => {
779
+ vi.useRealTimers();
780
+ const controller = new AbortController();
781
+ const cleanupCalled: string[] = [];
782
+
783
+ const acquireResource = (name: string): Promise<Result<string, Error>> =>
784
+ Promise.resolve(ok(`resource:${name}`));
785
+
786
+ const releaseResource = (name: string): void => {
787
+ cleanupCalled.push(name);
788
+ };
789
+
790
+ const result = await Eff.create<string, Error>(async ({ signal }) => {
791
+ // 获取资源
792
+ const resource = await acquireResource('db-connection');
793
+ if (resource.isErr()) return resource;
794
+
795
+ // 注册清理(通过 signal abort 事件或 finally)
796
+ const cleanup = () => releaseResource('db-connection');
797
+ signal.addEventListener('abort', cleanup);
798
+
799
+ try {
800
+ // 使用资源
801
+ await delay(10);
802
+ return ok(`done with ${resource._unsafeUnwrap()}`);
803
+ } finally {
804
+ // 确保清理
805
+ signal.removeEventListener('abort', cleanup);
806
+ cleanup();
807
+ }
808
+ }, controller.signal);
809
+
810
+ expect(result._unsafeUnwrap()).toBe('done with resource:db-connection');
811
+ expect(cleanupCalled).toContain('db-connection');
812
+
813
+ vi.useFakeTimers();
814
+ });
815
+ });
816
+
817
+ // ============================================================
818
+ // 边界情况
819
+ // ============================================================
820
+ describe('边界情况', () => {
821
+ test('单个 executor 的 all', async () => {
822
+ const controller = new AbortController();
823
+
824
+ const result = await Eff.all<number, Error>(
825
+ [async () => ok(42)],
826
+ controller.signal,
827
+ );
828
+
829
+ expect(result._unsafeUnwrap()).toEqual([42]);
830
+ });
831
+
832
+ test('混合快慢 executor', async () => {
833
+ vi.useRealTimers();
834
+ const controller = new AbortController();
835
+
836
+ const result = await Eff.all<number, Error>(
837
+ [
838
+ async () => ok(1), // 快
839
+ async () => {
840
+ await delay(10);
841
+ return ok(2);
842
+ }, // 慢
843
+ async () => ok(3), // 快
844
+ ],
845
+ controller.signal,
846
+ );
847
+
848
+ expect(result._unsafeUnwrap()).toEqual([1, 2, 3]);
849
+
850
+ vi.useFakeTimers();
851
+ });
852
+ });
853
+ });
854
+
855
+ // 辅助函数
856
+ function delay(ms: number): Promise<void> {
857
+ return new Promise((resolve) => setTimeout(resolve, ms));
858
+ }