t-tasks 1.2.1 → 2.1.0

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.
package/src/task.ts CHANGED
@@ -26,6 +26,7 @@ export type TaskFunction<A extends unknown[], R> = (...args: A) => Task<R>;
26
26
  /**
27
27
  * Task generator
28
28
  *
29
+ * @template T yielded underlying type
29
30
  * @template TT yielded task type
30
31
  * @template R returned task resolve type
31
32
  *
@@ -44,6 +45,7 @@ export type TaskGenerator<T, TT extends Task<T>, R> = Generator<TT, R, T>;
44
45
  * Function returning task generator (generator function)
45
46
  *
46
47
  * @template A argument types
48
+ * @template T yielded underlying type
47
49
  * @template TT yielded task type
48
50
  * @template R returned task resolve type
49
51
  *
@@ -178,6 +180,13 @@ export interface Task<R> extends TaskBase<R> {
178
180
  */
179
181
  matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }): Task<R2 | R3 | R4>;
180
182
 
183
+ /**
184
+ * Read the settled result when the task has already finished
185
+ *
186
+ * @returns settled result, or null when the task is still pending
187
+ */
188
+ resolved: () => Cancelable<R> | null;
189
+
181
190
  /**
182
191
  * Return underlying promise in order to await result
183
192
  *
@@ -186,16 +195,19 @@ export interface Task<R> extends TaskBase<R> {
186
195
  resolve: () => TaskInvoke<R>;
187
196
 
188
197
  /**
189
- * Invoke underlying canel method without error
198
+ * Invoke underlying cancel method without error
199
+ *
200
+ * @returns 'true' when the task was still pending and cancelation was applied
190
201
  */
191
- cancel: () => void;
202
+ cancel: () => boolean;
192
203
 
193
204
  /**
194
- * Invoke underlying canel method with error
205
+ * Invoke underlying cancel method with error
195
206
  *
196
207
  * @param error error value to be injected from outside
208
+ * @returns 'true' when the task was still pending and the error was applied
197
209
  */
198
- reject: (error: any) => void;
210
+ reject: (error: any) => boolean;
199
211
 
200
212
  /**
201
213
  * Wrap task to singleton generator
@@ -242,7 +254,7 @@ export namespace Task {
242
254
  * @note low-level primitive for creating custom tasks, not intended for general use
243
255
  */
244
256
  export function create<R>(invoke: TaskInvoke<R>, cancel: TaskCancel): Task<R> {
245
- return new TaskClass<R>(invoke, cancel);
257
+ return new TaskClass<R>({ resolved: false, invoke, cancel });
246
258
  }
247
259
 
248
260
  /**
@@ -252,8 +264,8 @@ export namespace Task {
252
264
  * @param value value to be returned upon awaiting
253
265
  * @returns task resolving to specified value
254
266
  */
255
- export function resolved<R>(value: R) {
256
- return Task.create<R>(Promise.resolve(Maybe.just(Either.right(value))), () => void undefined);
267
+ export function resolved<R>(value: R): Task<R> {
268
+ return new TaskClass<R>({ resolved: true, result: Maybe.just(Either.right(value)) });
257
269
  }
258
270
 
259
271
  /**
@@ -261,20 +273,20 @@ export namespace Task {
261
273
  *
262
274
  * @template R returned task's resolve type
263
275
  * @param error error to be returned upon awaiting
264
- * @returns task resolving to specified value
276
+ * @returns task rejected with the specified error
265
277
  */
266
- export function rejected<R>(error: any) {
267
- return Task.create<R>(Promise.resolve(Maybe.just(Either.left(error))), () => void undefined);
278
+ export function rejected<R>(error: any): Task<R> {
279
+ return new TaskClass<R>({ resolved: true, result: Maybe.just(Either.left(error)) });
268
280
  }
269
281
 
270
282
  /**
271
283
  * Invariant task constructor creating canceled task
272
284
  *
273
285
  * @template R returned task's resolve type
274
- * @returns task resolving to specified value
286
+ * @returns canceled task
275
287
  */
276
- export function canceled<R>() {
277
- return Task.create<R>(Promise.resolve(Maybe.nothing()), () => void undefined);
288
+ export function canceled<R>(): Task<R> {
289
+ return new TaskClass<R>({ resolved: true, result: Maybe.nothing() });
278
290
  }
279
291
 
280
292
  /**
@@ -294,19 +306,16 @@ export namespace Task {
294
306
  * ```
295
307
  */
296
308
  export function fromPromise<R>(promise: PromiseLike<R>): Task<R> {
297
- const [resolve, setResolve] = resolver<Cancelable<R>>();
309
+ const cell = settleCell<Cancelable<R>>();
298
310
 
299
- return Task.create(
300
- new Promise<Cancelable<R>>((_resolve) => {
301
- setResolve(_resolve);
311
+ const cancel: TaskCancel = (error: Maybe<any>) => cell.resolve(error.map(Either.left));
312
+
313
+ promise
314
+ .then(Either.right, Either.left)
315
+ .then(Maybe.just)
316
+ .then(cell.resolve);
302
317
 
303
- promise
304
- .then(Either.right, Either.left)
305
- .then(Maybe.just)
306
- .then(resolve);
307
- }),
308
- (error: Maybe<any>) => resolve(error.map(Either.left)),
309
- );
318
+ return Task.create(cell.promise, cancel);
310
319
  }
311
320
 
312
321
  /**
@@ -333,24 +342,120 @@ export namespace Task {
333
342
  * ```
334
343
  */
335
344
  export function fromFunction<R>(producer: () => R, cancelRef: { cancel: TaskCancel }): Task<R> {
336
- const [resolve, setResolve] = resolver<Cancelable<R>>();
345
+ const cell = settleCell<Cancelable<R>>();
337
346
 
338
- const cancel = (error: Maybe<any>) => resolve(error.map(Either.left));
347
+ const cancel: TaskCancel = (error: Maybe<any>) => cell.resolve(error.map(Either.left));
339
348
 
340
349
  cancelRef.cancel = cancel;
341
350
 
342
- return Task.create(
343
- new Promise<Cancelable<R>>((_resolve) => {
344
- setResolve(_resolve);
351
+ try {
352
+ cell.resolve(Maybe.just(Either.right(producer())));
353
+ } catch (e) {
354
+ cell.resolve(Maybe.just(Either.left(e)));
355
+ }
356
+
357
+ return Task.create(cell.promise, cancel);
358
+ }
359
+
360
+ /**
361
+ * Generic callback task
362
+ *
363
+ * Usefull for creating custom tasks from success and error callbacks and cancelaton function. Synchronous callbacks are not supported, @see fromFunction
364
+ *
365
+ * @template H cancelation handler type
366
+ * @template R returned task resolve type
367
+ * @param create task body with success and error callbacks
368
+ * @param onCancel cancelation function intended for stopping task execution
369
+ * @returns task resolving to success value
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * export function timeout(delay: number) {
374
+ * return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(() => resolve(), delay), clearTimeout);
375
+ * }
376
+ * ```
377
+ */
378
+ export function fromCallback<H, R>(create: (resolve: (result: R) => void, reject: (error: any) => void, cancel: () => void) => H, onCancel: (handler: H) => void): Task<R> {
379
+ const cell = settleCell<Cancelable<R>>();
380
+
381
+ let handler: { value: H } | null = null;
382
+
383
+ const cancel: TaskCancel = (error: Maybe<any>) => {
384
+ const wasPending = cell.resolve(error.map(Either.left));
385
+ if (wasPending && handler) {
386
+ onCancel(handler.value);
387
+ }
388
+ return wasPending;
389
+ }
390
+
391
+ try {
392
+ const value = create(
393
+ (r) => cell.resolve(Maybe.just(Either.right(r))),
394
+ (e) => cell.resolve(Maybe.just(Either.left(e))),
395
+ () => cancel(Maybe.nothing()),
396
+ );
397
+
398
+ handler = { value };
399
+
400
+ const result = cell.settled();
401
+ if (result && !result.value.match({ just: (v) => v.isRight(), nothing: () => false })) {
402
+ onCancel(value);
403
+ }
404
+ } catch (e) {
405
+ cell.resolve(Maybe.just(Either.left(e)));
406
+ }
407
+
408
+ return Task.create(cell.promise, cancel);
409
+ }
410
+
411
+ /**
412
+ * Lift an async operation that accepts an AbortSignal into a Task.
413
+ *
414
+ * Creates an internal AbortController and passes its signal to `producer`.
415
+ * If the resulting task is canceled or rejected externally, `controller.abort()`
416
+ * is called automatically.
417
+ *
418
+ * @template R returned task resolve type
419
+ * @param producer factory function receiving an AbortSignal
420
+ * @returns task wrapping the operation
421
+ *
422
+ * @example
423
+ * ```typescript
424
+ * const fetchUser = (id: string) =>
425
+ * Task.fromAbortSignal((signal) =>
426
+ * fetch(`/api/users/${id}`, { signal }).then((res) => res.json())
427
+ * );
428
+ * ```
429
+ */
430
+ export function fromAbortSignal<R>(
431
+ producer: (signal: AbortSignal) => PromiseLike<R>
432
+ ): Task<R> {
433
+ const cell = settleCell<Cancelable<R>>();
434
+ const controller = new AbortController();
435
+
436
+ const cancel: TaskCancel = (error: Maybe<any>) => {
437
+ const wasPending = cell.resolve(error.map(Either.left));
438
+ if (wasPending) {
439
+ controller.abort(error.match({ just: (err) => err, nothing: () => undefined }));
440
+ }
441
+ return wasPending;
442
+ };
345
443
 
346
- try {
347
- resolve(Maybe.just(Either.right(producer())));
348
- } catch (e) {
349
- resolve(Maybe.just(Either.left(e)));
444
+ try {
445
+ producer(controller.signal).then((value) => {
446
+ cell.resolve(Maybe.just(Either.right(value)));
447
+ }, (error) => {
448
+ if (controller.signal.aborted && (error?.name === 'AbortError' || (typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError'))) {
449
+ cell.resolve(Maybe.nothing());
450
+ } else {
451
+ cell.resolve(Maybe.just(Either.left(error)));
350
452
  }
351
- }),
352
- cancel,
353
- );
453
+ });
454
+ } catch (e) {
455
+ cell.resolve(Maybe.just(Either.left(e)));
456
+ }
457
+
458
+ return Task.create(cell.promise, cancel);
354
459
  }
355
460
 
356
461
  /**
@@ -367,9 +472,8 @@ export namespace Task {
367
472
  /**
368
473
  * Convinience shortcut for yielding timers
369
474
  *
370
- * @template R returned generator resolve type
371
- * @param promise promise to be resolved
372
- * @returns generator to be be used with yield*
475
+ * @param delay duration in ms after that the generator resolves
476
+ * @returns generator to be used with yield*
373
477
  */
374
478
  export function timeoutGenerator(delay: number) {
375
479
  return Task.timeout(delay).generator();
@@ -407,6 +511,7 @@ export namespace Task {
407
511
  *
408
512
  * If error is not caught the task is interrupted and returns 'just left error' immediately. In case of cancelation the task is interrupted and returns 'nothing' immediately
409
513
  *
514
+ * @template T yielded underlying type
410
515
  * @template TT yielded task type
411
516
  * @template R returned task resolve type
412
517
  * @param taskGeneratorFunction task generator function
@@ -431,21 +536,50 @@ export namespace Task {
431
536
  * ```
432
537
  */
433
538
  export function generate<T, TT extends Task<T>, R>(taskGeneratorFunction: TaskGeneratorFunction<[], T, TT, R>): Task<R> {
434
- const generator = taskGeneratorFunction();
435
-
436
- const sequentor = (next: IteratorResult<TT, R>): Task<R> => {
437
- return next.done ? (
438
- Task.resolved(next.value)
439
- ) : (
440
- next.value.matchChain<R>({
441
- resolved: (value) => sequentor(generator.next(value)),
442
- rejected: (error) => sequentor(generator.throw(error)),
443
- canceled: Task.canceled,
444
- })
445
- );
539
+ const generator: TaskGenerator<T, TT, R> = taskGeneratorFunction();
540
+ const cell = settleCell<Cancelable<R>>();
541
+
542
+ let current: { task: Task<T>; error: Maybe<any> | null } | null = null;
543
+
544
+ const sequentor = (invoke: () => IteratorResult<TT, R>, override?: Cancelable<R>) => {
545
+ try {
546
+ const next = invoke();
547
+
548
+ if (next.done) {
549
+ cell.resolve(override ?? current?.error?.map(Either.left) ?? Maybe.just(Either.right(next.value)));
550
+ current = null;
551
+ return;
552
+ }
553
+
554
+ current = {
555
+ task: next.value,
556
+ error: null,
557
+ };
558
+
559
+ next.value._invoke((maybe) => maybe.matchTap({
560
+ just: (either) => either.matchTap({
561
+ right: (value) => sequentor(() => generator.next(value), override),
562
+ left: (error) => sequentor(() => generator.throw(error), override),
563
+ }),
564
+ nothing: () => sequentor(() => generator.return(undefined as R), Maybe.nothing()),
565
+ }))
566
+ } catch (error) {
567
+ cell.resolve(override ?? Maybe.just(Either.left(error)));
568
+ current = null;
569
+ }
446
570
  };
447
571
 
448
- return Task.resolved(undefined).chain(() => sequentor(generator.next()));
572
+ sequentor(() => generator.next());
573
+
574
+ return Task.create(cell.promise, (error) => {
575
+ if (cell.settled() || !current) {
576
+ return false;
577
+ }
578
+
579
+ current.error = error;
580
+
581
+ return current.task._cancel(error);
582
+ });
449
583
  }
450
584
 
451
585
  /**
@@ -453,16 +587,15 @@ export namespace Task {
453
587
  *
454
588
  * Simplifies wrighting functions returning generated tasks
455
589
  *
456
- * @template Args generator arguments
590
+ * @template A generator arguments
591
+ * @template T yielded underlying type
457
592
  * @template TT yielded task type
458
593
  * @template R returned task resolve type
459
594
  * @param taskGeneratorFunction function* (...args) task generator function
460
- * @returns task resolving to generator's return type
595
+ * @returns task function resolving to generator's return type
461
596
  */
462
597
  export function generateFunction<A extends any[], T, TT extends Task<T>, R>(taskGeneratorFunction: TaskGeneratorFunction<A, T, TT, R>): TaskFunction<A, R> {
463
- return (...args: A) => generate(function* () {
464
- return yield* taskGeneratorFunction(...args);
465
- });
598
+ return (...args: A) => generate(() => taskGeneratorFunction(...args));
466
599
  }
467
600
 
468
601
  /**
@@ -483,51 +616,13 @@ export namespace Task {
483
616
  * ```
484
617
  */
485
618
  export function timeout(delay: number) {
486
- return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(() => resolve(), delay), clearTimeout);
487
- }
488
-
489
- /**
490
- * Generic callback task
491
- *
492
- * Usefull for creating custom tasks from success and error callbacks and cancelaton function. Synchronous callbacks are not supported, @see fromFuction
493
- *
494
- * @param create task body with success and error callbacks
495
- * @param cancel cancelation function intended for stopping task execution
496
- * @returns task resolving to success value
497
- *
498
- * @example
499
- * ```typescript
500
- * export function timeout(delay: number) {
501
- * return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(() => resolve(), delay), clearTimeout);
502
- * }
503
- * ```
504
- */
505
- export function fromCallback<H, R>(create: (resolve: (result: R) => void, reject: (error: any) => void, cancel: () => void) => H, onCancel: (handler: H) => void): Task<R> {
506
- let handler: H;
507
-
508
- const [resolve, setResolve] = resolver<Cancelable<R>>();
509
-
510
- return Task.create(
511
- new Promise<Cancelable<R>>((_resolve) => {
512
- setResolve(_resolve);
513
-
514
- handler = create(
515
- (r) => resolve(Maybe.just(Either.right(r))),
516
- (e) => resolve(Maybe.just(Either.left(e))),
517
- () => resolve(Maybe.nothing())
518
- );
519
- }),
520
- (error: Maybe<any>) => {
521
- resolve(error.map(Either.left));
522
-
523
- onCancel(handler);
524
- },
525
- );
619
+ return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(resolve, delay), clearTimeout);
526
620
  }
527
621
 
528
622
  /**
529
623
  * Start multiple tasks one after another
530
624
  *
625
+ * @template T underlying task type
531
626
  * @param taskFunctions list of task functions (without arguments)
532
627
  * @returns composite task invoring every task in order and resolving to the list of results
533
628
  */
@@ -550,47 +645,38 @@ export namespace Task {
550
645
  *
551
646
  * @template T underlying task type
552
647
  * @param tasks iterable of tasks to execute
553
- * @retuirns task resolving to the list of results
648
+ * @returns task resolving to the list of results
554
649
  */
555
650
  export function all<T>(tasks: Iterable<Task<T>>) {
556
- const [resolve, setResolve] = resolver<Cancelable<T[]>>();
557
-
558
651
  const taskArray = Array.from(tasks);
559
-
560
- const cancel = (error: Maybe<any>) => taskArray.forEach((task) => task._cancel(error));
561
-
562
- const list = taskArray.map<Maybe<T>>(Maybe.nothing);
563
-
564
- const resolved = (value: T, i: number) => {
565
- list[i] = Maybe.just(value);
566
-
567
- if (Maybe.everyJust(list)) {
568
- resolve(Maybe.just(Either.right(list.map(Just.just))));
569
- }
570
- };
571
- const rejected = (error: Maybe<any>) => {
572
- resolve(error.map(Either.left));
573
- cancel(error);
574
- };
575
-
576
- return Task.create(
577
- new Promise<Cancelable<T[]>>((_resolve) => {
578
- setResolve(_resolve);
579
-
580
- if (taskArray.length < 1) {
581
- return resolve(Maybe.just(Either.right([])));
582
- }
583
-
584
- taskArray.map((task, i) => {
585
- return task.matchTap({
586
- resolved: (value) => resolved(value, i),
587
- rejected: (error) => rejected(Maybe.just(error)),
588
- canceled: () => rejected(Maybe.nothing()),
589
- });
652
+ if (taskArray.length === 0) {
653
+ return Task.resolved([]);
654
+ }
655
+
656
+ return Task.fromCallback<Task<T>[], T[]>((resolve, reject, cancel) => {
657
+ const results: T[] = new Array(taskArray.length);
658
+ let remaining = taskArray.length;
659
+
660
+ taskArray.forEach((task, i) => {
661
+ task.matchTap({
662
+ resolved: (value) => {
663
+ results[i] = value;
664
+ remaining--;
665
+ if (remaining === 0) resolve(results);
666
+ },
667
+ rejected: (error) => {
668
+ reject(error);
669
+ taskArray.forEach((t) => t.cancel());
670
+ },
671
+ canceled: () => {
672
+ cancel();
673
+ taskArray.forEach((t) => t.cancel());
674
+ },
590
675
  });
591
- }),
592
- cancel,
593
- );
676
+ });
677
+
678
+ return taskArray;
679
+ }, (subtasks) => subtasks.forEach((t) => t.cancel()));
594
680
  }
595
681
 
596
682
  /**
@@ -605,47 +691,43 @@ export namespace Task {
605
691
  *
606
692
  * @template T underlying task type
607
693
  * @param tasks iterable of tasks to execute
608
- * @retuirns task resolving to the result of first successfull task
694
+ * @returns task resolving to the result of first successfull task
609
695
  */
610
696
  export function any<T>(tasks: Iterable<Task<T>>) {
611
- const [resolve, setResolve] = resolver<Cancelable<T>>();
697
+ const cell = settleCell<Cancelable<T>>();
612
698
 
613
699
  const taskArray = Array.from(tasks);
614
700
 
615
- const cancel = (error: Maybe<any>) => taskArray.forEach((task) => task._cancel(error));
701
+ if (taskArray.length < 1) {
702
+ return Task.canceled();
703
+ }
616
704
 
617
- const list = taskArray.map<Maybe<any>>(Maybe.nothing);
705
+ const cancel = (error: Maybe<any>) => taskArray.map((task) => task._cancel(error)).every((r) => r);
706
+
707
+ const list = taskArray.map<Maybe<any>>(() => Maybe.nothing());
618
708
 
619
709
  const resolved = (value: Maybe<T>) => {
620
- resolve(value.map(Either.right));
710
+ cell.resolve(value.map(Either.right));
621
711
  cancel(Maybe.nothing());
622
712
  };
713
+
623
714
  const rejected = (error: any, i: number) => {
624
715
  list[i] = Maybe.just(error);
625
716
 
626
717
  if (Maybe.everyJust(list)) {
627
- resolve(Maybe.just(Either.left(list.map(Just.just))));
718
+ cell.resolve(Maybe.just(Either.left(list.map((v) => Just.just(v)))));
628
719
  }
629
720
  };
630
721
 
631
- return Task.create(
632
- new Promise<Cancelable<T>>((_resolve) => {
633
- setResolve(_resolve);
634
-
635
- if (taskArray.length < 1) {
636
- return resolve(Maybe.nothing());
637
- }
722
+ taskArray.map((task, i) => {
723
+ return task.matchTap({
724
+ resolved: (value) => resolved(Maybe.just(value)),
725
+ rejected: (error) => rejected(error, i),
726
+ canceled: () => resolved(Maybe.nothing()),
727
+ });
728
+ });
638
729
 
639
- taskArray.map((task, i) => {
640
- return task.matchTap({
641
- resolved: (value) => resolved(Maybe.just(value)),
642
- rejected: (error) => rejected(error, i),
643
- canceled: () => resolved(Maybe.nothing()),
644
- });
645
- });
646
- }),
647
- cancel,
648
- );
730
+ return Task.create(cell.promise, cancel);
649
731
  }
650
732
 
651
733
  /**
@@ -654,6 +736,7 @@ export namespace Task {
654
736
  * If the second task resolves sooner than the first, resulting task is canceled
655
737
  * Otherwise the resulting task resolves together with the first one
656
738
  *
739
+ * @template T underlying task type
657
740
  * @param task task to limit
658
741
  * @param limitTask limiting task
659
742
  * @returns task limites according to limitTask
@@ -668,7 +751,9 @@ export namespace Task {
668
751
  * Given task is re-generated in case of failure (both external and internal)
669
752
  * Resulting task is still cancelable by standard means
670
753
  *
754
+ * @template T underlying task type
671
755
  * @param taskFunction task preoducing function
756
+ * @returns task that retries until the produced task resolves
672
757
  */
673
758
  export function repeat<T>(taskFunction: TaskFunction<[], T>) {
674
759
  const sequentor = (): Task<T> => taskFunction().chainRejected(sequentor);
@@ -682,152 +767,281 @@ export namespace Task {
682
767
  /// --------------------------------------------------------------------------------------
683
768
 
684
769
  type TaskInvoke<R> = Promise<Cancelable<R>>;
685
- type TaskCancel = (error: Maybe<any>) => void;
770
+ type TaskCancel = (error: Maybe<any>) => boolean;
686
771
 
687
772
  /**
688
773
  * @hidden
689
774
  */
690
775
  interface TaskBase<R> {
691
- /**
692
- * @hidden
693
- */
694
- readonly _invoke: TaskInvoke<R>;
695
- /**
696
- * @hidden
697
- */
698
- readonly _cancel: TaskCancel;
776
+ _invoke(op: (result: Cancelable<R>) => void): void;
777
+ _cancel(error: Maybe<any>): boolean;
699
778
  }
700
779
 
701
- const resolver = <T>() => {
702
- const stub: (t: T) => void = () => void undefined;
703
-
704
- let globalResolve = stub;
780
+ /**
781
+ * One-shot settlement cell.
782
+ *
783
+ * The Promise is created internally, so there is no unarmed window: `resolve`
784
+ * before or after construction always owns the result (first writer wins).
785
+ * Later `resolve` calls are ignored. Reentrant `resolve` from the sink is ignored.
786
+ *
787
+ * `settled` is for skipping continuations; it does not cancel children by itself.
788
+ * After creating a child, if `settled` is already true, cancel that child with
789
+ * the stored value. Bind `_cancel` to the child only after that check.
790
+ */
791
+ type SettleCell<T> = {
792
+ readonly promise: Promise<T>;
793
+ readonly resolve: (value: T) => boolean;
794
+ readonly settled: () => { value: T } | null;
795
+ };
705
796
 
706
- const resolve = (value: T) => {
707
- const r = globalResolve;
797
+ const stub = () => void undefined;
708
798
 
709
- globalResolve = stub;
799
+ const settleCell = <T>(): SettleCell<T> => {
800
+ let sink: (value: T) => void = stub;
801
+ let settled: { value: T } | null = null;
710
802
 
711
- r(value);
712
- };
803
+ const promise = new Promise<T>((resolve) => {
804
+ sink = resolve;
805
+ });
713
806
 
714
- const setResolve = (value: typeof resolve) => {
715
- globalResolve = value;
716
- };
807
+ const resolve = (next: T) => {
808
+ if (settled) {
809
+ return false;
810
+ }
717
811
 
718
- return [resolve, setResolve] as [typeof resolve, typeof setResolve];
719
- };
812
+ settled = { value: next };
720
813
 
721
- function chainTaskMaybe<R, R2>(_task: TaskBase<R>, op: (value: Cancelable<R>) => Task<R2>) {
722
- const globalCancel = { cancel: _task._cancel };
814
+ sink(next);
723
815
 
724
- return Task.create(
725
- _task._invoke.then(async (result) => {
726
- // Tecnically it's not possible to throw here anymore as op is lifted into task now, but just in case
727
- try {
728
- const { _invoke: invoke2, _cancel: cancel2 } = Task.fromFunction(() => op(result), globalCancel);
816
+ return true;
817
+ };
729
818
 
730
- globalCancel.cancel = cancel2;
819
+ return {
820
+ promise,
821
+ resolve,
822
+ settled: () => settled,
823
+ };
824
+ };
731
825
 
732
- const produced = await invoke2;
826
+ function chainTaskMaybe<R, R2>(parent: Task<R>, op: (value: Cancelable<R>) => Task<R2>): Task<R2> {
827
+ const cell = settleCell<Cancelable<R2>>();
828
+
829
+ let state:
830
+ | { phase: 'parent' }
831
+ | { phase: 'transform', error: Maybe<any> | null }
832
+ | { phase: 'child', cancel: TaskCancel }
833
+ | { phase: 'closed' } = { phase: 'parent' };
834
+
835
+ parent._invoke((result) => {
836
+ state = { phase: 'transform', error: null }
837
+
838
+ let child: Task<R2> | null = null;
839
+ try {
840
+ child = op(result);
841
+ } catch (e) {
842
+ cell.resolve(Maybe.just(Either.left(e)));
843
+ state = { phase: 'closed' };
844
+ return;
845
+ }
846
+
847
+ const transformError = state.phase === 'transform' ? state.error : null;
848
+
849
+ state = { phase: 'child', cancel: (error) => child._cancel(error) };
850
+
851
+ if (transformError) {
852
+ cell.resolve(transformError.map(Either.left));
853
+ state = { phase: 'closed' };
854
+ child._cancel(transformError);
855
+ return;
856
+ }
857
+
858
+ return child._invoke((value) => {
859
+ cell.resolve(value);
860
+ state = { phase: 'closed' };
861
+ });
862
+ });
733
863
 
734
- if (produced.isNothing()) {
735
- return Maybe.nothing();
736
- }
864
+ return Task.create(cell.promise, (error) => {
865
+ if (cell.settled()) {
866
+ return false;
867
+ }
868
+
869
+ if (state.phase === 'parent') {
870
+ return parent._cancel(error);
871
+ } else if (state.phase === 'transform') {
872
+ state.error = error;
873
+ return true;
874
+ } else if (state.phase === 'child') {
875
+ return state.cancel(error);
876
+ } else {
877
+ return false;
878
+ }
879
+ });
880
+ }
737
881
 
738
- if (produced.just.isLeft()) {
739
- return Maybe.just(Either.left(produced.just.left));
740
- }
882
+ function mapTaskMaybe<R, R2>(parent: Task<R>, op: (value: Cancelable<R>) => Cancelable<R2>): Task<R2> {
883
+ const cell = settleCell<Cancelable<R2>>();
884
+
885
+ let state:
886
+ | { phase: 'parent' }
887
+ | { phase: 'transform', error: Maybe<any> | null }
888
+ | { phase: 'closed' } = { phase: 'parent' };
889
+
890
+ parent._invoke((result) => {
891
+ state = { phase: 'transform', error: null }
892
+
893
+ let value: Cancelable<R2> | null = null;
894
+ try {
895
+ value = op(result);
896
+ } catch (e) {
897
+ cell.resolve(Maybe.just(Either.left(e)));
898
+ state = { phase: 'closed' };
899
+ return;
900
+ }
901
+
902
+ const transformError = state.phase === 'transform' ? state.error : null;
903
+
904
+ if (transformError) {
905
+ cell.resolve(transformError.map(Either.left));
906
+ } else {
907
+ cell.resolve(value);
908
+ }
909
+
910
+ state = { phase: 'closed' };
911
+ });
741
912
 
742
- const { _invoke: invoke3, _cancel: cancel3 } = produced.just.right;
913
+ return Task.create(cell.promise, (error) => {
914
+ if (cell.settled()) {
915
+ return false;
916
+ }
917
+
918
+ if (state.phase === 'parent') {
919
+ return parent._cancel(error);
920
+ } else if (state.phase === 'transform') {
921
+ state.error = error;
922
+ return true;
923
+ } else {
924
+ return false;
925
+ }
926
+ });
927
+ }
743
928
 
744
- globalCancel.cancel = cancel3;
929
+ function tapTaskMaybe<R>(parent: Task<R>, op: (value: Cancelable<R>) => void): Task<R> {
930
+ parent._invoke((result) => {
931
+ try {
932
+ op(result);
933
+ } catch (e) {
934
+ reportError(e);
935
+ }
936
+ });
745
937
 
746
- return invoke3;
747
- } catch (e) {
748
- return Maybe.just(Either.left(e));
749
- }
750
- }),
751
- (error: Maybe<any>) => globalCancel.cancel(error),
752
- );
938
+ return parent;
753
939
  }
754
940
 
755
- function chainTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) => Task<R2>) {
941
+ function chainTaskEither<R, R2>(_task: Task<R>, op: (value: Rejectable<R>) => Task<R2>): Task<R2> {
756
942
  return chainTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op).orMap<Task<R2>>(Task.canceled).just);
757
943
  }
758
944
 
759
- function mapTaskMaybe<R, R2>(_task: TaskBase<R>, op: (value: Cancelable<R>) => Cancelable<R2>) {
760
- return chainTaskMaybe<R, R2>(_task, (maybe) => {
761
- return op(maybe).matchMap<Task<R2>>({
762
- just: (either) =>
763
- either.matchMap<Task<R2>>({
764
- right: Task.resolved,
765
- left: Task.rejected,
766
- }).right,
767
- nothing: Task.canceled,
768
- }).just;
769
- });
770
- }
771
-
772
- function mapTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) => Rejectable<R2>) {
945
+ function mapTaskEither<R, R2>(_task: Task<R>, op: (value: Rejectable<R>) => Rejectable<R2>): Task<R2> {
773
946
  return mapTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op));
774
947
  }
775
948
 
776
- function tapTaskMaybe<R>(_task: TaskBase<R>, op: (value: Cancelable<R>) => void) {
777
- return mapTaskMaybe<R, R>(_task, (value) => {
778
- op(value);
779
-
780
- return value;
781
- });
782
- }
783
-
784
- function tapTaskEither<R>(_task: TaskBase<R>, op: (value: Rejectable<R>) => void) {
949
+ function tapTaskEither<R>(_task: Task<R>, op: (value: Rejectable<R>) => void): Task<R> {
785
950
  return tapTaskMaybe<R>(_task, (maybe) => maybe.tap(op));
786
951
  }
787
952
 
953
+ type SyncState<R> =
954
+ | { resolved: false; invoke: TaskInvoke<R>; cancel: TaskCancel }
955
+ | { resolved: true; result: Cancelable<R> }
956
+
788
957
  class TaskClass<R> implements Task<R> {
789
- constructor(readonly _invoke: TaskInvoke<R>, readonly _cancel: TaskCancel) {}
958
+ constructor(private _state: SyncState<R>) {
959
+ if (!this._state.resolved) {
960
+ this._state.invoke = this._state.invoke.then((result) => {
961
+ if (!this._state.resolved) {
962
+ this._state = { resolved: true, result }
963
+ }
964
+ return result;
965
+ });
966
+ }
967
+ }
790
968
 
791
- tapCanceled(op: () => void) {
969
+ _invoke(op: (result: Cancelable<R>) => void) {
970
+ if (this._state.resolved) {
971
+ return op(this._state.result);
972
+ } else {
973
+ return this._state.invoke.then(op);
974
+ }
975
+ }
976
+
977
+ _cancel(error: Maybe<any>) {
978
+ if (this._state.resolved) {
979
+ return false
980
+ } else {
981
+ return this._state.cancel(error);
982
+ }
983
+ }
984
+
985
+ tapCanceled(op: () => void): Task<R> {
792
986
  return tapTaskMaybe<R>(this, (maybe) => maybe.orTap(op));
793
987
  }
794
- tapRejected(op: (error: any) => void) {
988
+ tapRejected(op: (error: any) => void): Task<R> {
795
989
  return tapTaskEither<R>(this, (either) => either.orTap(op));
796
990
  }
797
- tap(op: (value: R) => void) {
991
+ tap(op: (value: R) => void): Task<R> {
798
992
  return tapTaskEither<R>(this, (either) => either.tap(op));
799
993
  }
800
994
 
801
- mapCanceled<R2>(op: () => R2) {
995
+ mapCanceled<R2>(op: () => R2): Task<R | R2> {
802
996
  return mapTaskMaybe<R, R | R2>(this, (maybe) => maybe.orMap(() => Either.right(op())));
803
997
  }
804
- mapRejected<R2>(op: (error: any) => R2) {
998
+ mapRejected<R2>(op: (error: any) => R2): Task<R | R2> {
805
999
  return mapTaskEither<R, R | R2>(this, (either) => either.orMap(op));
806
1000
  }
807
- map<R2>(op: (value: R) => R2) {
1001
+ map<R2>(op: (value: R) => R2): Task<R2> {
808
1002
  return mapTaskEither<R, R2>(this, (either) => either.map(op));
809
1003
  }
810
1004
 
811
- chainCanceled<R2>(op: () => Task<R2>) {
1005
+ chainCanceled<R2>(op: () => Task<R2>): Task<R | R2> {
812
1006
  const mapEither = (either: Rejectable<R>) => either.map(Task.resolved).orMap<Task<R>>(Task.rejected).right;
813
1007
 
814
1008
  return chainTaskMaybe<R, R | R2>(this, (maybe) => maybe.map(mapEither).orMap(op).just);
815
1009
  }
816
- chainRejected<R2>(op: (error: any) => Task<R2>) {
1010
+ chainRejected<R2>(op: (error: any) => Task<R2>): Task<R | R2> {
817
1011
  return chainTaskEither<R, R | R2>(this, (either) => either.map(Task.resolved).orMap(op).right);
818
1012
  }
819
- chain<R2>(op: (value: R) => Task<R2>) {
1013
+ chain<R2>(op: (value: R) => Task<R2>): Task<R2> {
820
1014
  return chainTaskEither<R, R2>(this, (either) => either.map(op).orMap<Task<R2>>(Task.rejected).right);
821
1015
  }
822
1016
 
1017
+ resolved() {
1018
+ if (this._state.resolved) {
1019
+ return this._state.result;
1020
+ } else {
1021
+ return null;
1022
+ }
1023
+ }
1024
+
823
1025
  resolve() {
824
- return this._invoke;
1026
+ if (this._state.resolved) {
1027
+ return Promise.resolve(this._state.result);
1028
+ } else {
1029
+ return this._state.invoke;
1030
+ }
825
1031
  }
826
1032
  cancel() {
827
- return this._cancel(Maybe.nothing());
1033
+ if (this._state.resolved) {
1034
+ return false;
1035
+ } else {
1036
+ return this._state.cancel(Maybe.nothing());
1037
+ }
828
1038
  }
829
1039
  reject(error: any) {
830
- return this._cancel(Maybe.just(error));
1040
+ if (this._state.resolved) {
1041
+ return false;
1042
+ } else {
1043
+ return this._state.cancel(Maybe.just(error));
1044
+ }
831
1045
  }
832
1046
 
833
1047
  generator() {
@@ -836,7 +1050,7 @@ class TaskClass<R> implements Task<R> {
836
1050
  })(this);
837
1051
  }
838
1052
 
839
- matchTap(op: { resolved: (value: R) => void; rejected: (error: any) => void; canceled: () => void }) {
1053
+ matchTap(op: { resolved: (value: R) => void; rejected: (error: any) => void; canceled: () => void }): Task<R> {
840
1054
  return tapTaskMaybe(this, (maybe) => {
841
1055
  return maybe.matchTap({
842
1056
  just: (either) => {
@@ -850,31 +1064,37 @@ class TaskClass<R> implements Task<R> {
850
1064
  });
851
1065
  }
852
1066
 
853
- matchMap<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => R2; rejected: (error: any) => R3; canceled: () => R4 }) {
1067
+ matchMap<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => R2; rejected: (error: any) => R3; canceled: () => R4 }): Task<R2 | R3 | R4> {
854
1068
  return mapTaskMaybe(this, (maybe) => {
855
- return maybe.matchMap<Right<R2 | R3 | R4, any>>({
1069
+ return Maybe.just(maybe.match<Right<R2 | R3 | R4, any>>({
856
1070
  just: (either) => {
857
- return either.matchMap({
1071
+ return Either.right(either.match({
858
1072
  right: op.resolved,
859
1073
  left: op.rejected,
860
- });
1074
+ }));
861
1075
  },
862
1076
  nothing: () => Either.right(op.canceled()),
863
- });
1077
+ }));
864
1078
  });
865
1079
  }
866
1080
 
867
- matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }) {
1081
+ matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }): Task<R2 | R3 | R4> {
868
1082
  return chainTaskMaybe(this, (maybe) => {
869
- return maybe.matchMap<Task<R2 | R3 | R4>>({
1083
+ return maybe.match<Task<R2 | R3 | R4>>({
870
1084
  just: (either) => {
871
- return either.matchMap<Task<R2 | R3>>({
1085
+ return either.match<Task<R2 | R3>>({
872
1086
  right: op.resolved,
873
1087
  left: op.rejected,
874
- }).right;
1088
+ });
875
1089
  },
876
1090
  nothing: op.canceled,
877
- }).just;
1091
+ });
878
1092
  });
879
1093
  }
880
1094
  }
1095
+
1096
+ function reportError(error: unknown): void {
1097
+ if (typeof globalThis.reportError === 'function') {
1098
+ globalThis.reportError(error);
1099
+ }
1100
+ }