t-tasks 1.2.0 → 2.0.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
@@ -178,6 +178,8 @@ export interface Task<R> extends TaskBase<R> {
178
178
  */
179
179
  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
180
 
181
+ resolved: () => Cancelable<R> | null;
182
+
181
183
  /**
182
184
  * Return underlying promise in order to await result
183
185
  *
@@ -188,14 +190,14 @@ export interface Task<R> extends TaskBase<R> {
188
190
  /**
189
191
  * Invoke underlying canel method without error
190
192
  */
191
- cancel: () => void;
193
+ cancel: () => boolean;
192
194
 
193
195
  /**
194
196
  * Invoke underlying canel method with error
195
197
  *
196
198
  * @param error error value to be injected from outside
197
199
  */
198
- reject: (error: any) => void;
200
+ reject: (error: any) => boolean;
199
201
 
200
202
  /**
201
203
  * Wrap task to singleton generator
@@ -242,7 +244,7 @@ export namespace Task {
242
244
  * @note low-level primitive for creating custom tasks, not intended for general use
243
245
  */
244
246
  export function create<R>(invoke: TaskInvoke<R>, cancel: TaskCancel): Task<R> {
245
- return new TaskClass<R>(invoke, cancel);
247
+ return new TaskClass<R>({ resolved: false, invoke, cancel });
246
248
  }
247
249
 
248
250
  /**
@@ -252,8 +254,8 @@ export namespace Task {
252
254
  * @param value value to be returned upon awaiting
253
255
  * @returns task resolving to specified value
254
256
  */
255
- export function resolved<R>(value: R) {
256
- return Task.create<R>(Promise.resolve(Maybe.just(Either.right(value))), () => void undefined);
257
+ export function resolved<R>(value: R): Task<R> {
258
+ return new TaskClass<R>({ resolved: true, result: Maybe.just(Either.right(value)) });
257
259
  }
258
260
 
259
261
  /**
@@ -263,8 +265,8 @@ export namespace Task {
263
265
  * @param error error to be returned upon awaiting
264
266
  * @returns task resolving to specified value
265
267
  */
266
- export function rejected<R>(error: any) {
267
- return Task.create<R>(Promise.resolve(Maybe.just(Either.left(error))), () => void undefined);
268
+ export function rejected<R>(error: any): Task<R> {
269
+ return new TaskClass<R>({ resolved: true, result: Maybe.just(Either.left(error)) });
268
270
  }
269
271
 
270
272
  /**
@@ -273,8 +275,8 @@ export namespace Task {
273
275
  * @template R returned task's resolve type
274
276
  * @returns task resolving to specified value
275
277
  */
276
- export function canceled<R>() {
277
- return Task.create<R>(Promise.resolve(Maybe.nothing()), () => void undefined);
278
+ export function canceled<R>(): Task<R> {
279
+ return new TaskClass<R>({ resolved: true, result: Maybe.nothing() });
278
280
  }
279
281
 
280
282
  /**
@@ -294,19 +296,16 @@ export namespace Task {
294
296
  * ```
295
297
  */
296
298
  export function fromPromise<R>(promise: PromiseLike<R>): Task<R> {
297
- const [resolve, setResolve] = resolver<Cancelable<R>>();
299
+ const cell = settleCell<Cancelable<R>>();
298
300
 
299
- return Task.create(
300
- new Promise<Cancelable<R>>((_resolve) => {
301
- setResolve(_resolve);
301
+ const cancel: TaskCancel = (error: Maybe<any>) => cell.resolve(error.map(Either.left));
302
+
303
+ promise
304
+ .then(Either.right, Either.left)
305
+ .then(Maybe.just)
306
+ .then(cell.resolve);
302
307
 
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
- );
308
+ return Task.create(cell.promise, cancel);
310
309
  }
311
310
 
312
311
  /**
@@ -333,24 +332,118 @@ export namespace Task {
333
332
  * ```
334
333
  */
335
334
  export function fromFunction<R>(producer: () => R, cancelRef: { cancel: TaskCancel }): Task<R> {
336
- const [resolve, setResolve] = resolver<Cancelable<R>>();
335
+ const cell = settleCell<Cancelable<R>>();
337
336
 
338
- const cancel = (error: Maybe<any>) => resolve(error.map(Either.left));
337
+ const cancel: TaskCancel = (error: Maybe<any>) => cell.resolve(error.map(Either.left));
339
338
 
340
339
  cancelRef.cancel = cancel;
341
340
 
342
- return Task.create(
343
- new Promise<Cancelable<R>>((_resolve) => {
344
- setResolve(_resolve);
341
+ try {
342
+ cell.resolve(Maybe.just(Either.right(producer())));
343
+ } catch (e) {
344
+ cell.resolve(Maybe.just(Either.left(e)));
345
+ }
346
+
347
+ return Task.create(cell.promise, cancel);
348
+ }
349
+
350
+ /**
351
+ * Generic callback task
352
+ *
353
+ * Usefull for creating custom tasks from success and error callbacks and cancelaton function. Synchronous callbacks are not supported, @see fromFuction
354
+ *
355
+ * @param create task body with success and error callbacks
356
+ * @param cancel cancelation function intended for stopping task execution
357
+ * @returns task resolving to success value
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * export function timeout(delay: number) {
362
+ * return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(() => resolve(), delay), clearTimeout);
363
+ * }
364
+ * ```
365
+ */
366
+ export function fromCallback<H, R>(create: (resolve: (result: R) => void, reject: (error: any) => void, cancel: () => void) => H, onCancel: (handler: H) => void): Task<R> {
367
+ const cell = settleCell<Cancelable<R>>();
368
+
369
+ let handler: { value: H } | null = null;
370
+
371
+ const cancel: TaskCancel = (error: Maybe<any>) => {
372
+ const wasPending = cell.resolve(error.map(Either.left));
373
+ if (wasPending && handler) {
374
+ onCancel(handler.value);
375
+ }
376
+ return wasPending;
377
+ }
378
+
379
+ try {
380
+ const value = create(
381
+ (r) => cell.resolve(Maybe.just(Either.right(r))),
382
+ (e) => cell.resolve(Maybe.just(Either.left(e))),
383
+ () => cancel(Maybe.nothing()),
384
+ );
385
+
386
+ handler = { value };
387
+
388
+ const result = cell.settled();
389
+ if (result && !result.value.match({ just: (v) => v.isRight(), nothing: () => false })) {
390
+ onCancel(value);
391
+ }
392
+ } catch (e) {
393
+ cell.resolve(Maybe.just(Either.left(e)));
394
+ }
395
+
396
+ return Task.create(cell.promise, cancel);
397
+ }
398
+
399
+ /**
400
+ * Lift an async operation that accepts an AbortSignal into a Task.
401
+ *
402
+ * Creates an internal AbortController and passes its signal to `producer`.
403
+ * If the resulting task is canceled or rejected externally, `controller.abort()`
404
+ * is called automatically.
405
+ *
406
+ * @template R returned task resolve type
407
+ * @param producer factory function receiving an AbortSignal
408
+ * @returns task wrapping the operation
409
+ *
410
+ * @example
411
+ * ```typescript
412
+ * const fetchUser = (id: string) =>
413
+ * Task.fromAbortSignal((signal) =>
414
+ * fetch(`/api/users/${id}`, { signal }).then((res) => res.json())
415
+ * );
416
+ * ```
417
+ */
418
+ export function fromAbortSignal<R>(
419
+ producer: (signal: AbortSignal) => PromiseLike<R>
420
+ ): Task<R> {
421
+ const cell = settleCell<Cancelable<R>>();
422
+ const controller = new AbortController();
423
+
424
+ const cancel: TaskCancel = (error: Maybe<any>) => {
425
+ const wasPending = cell.resolve(error.map(Either.left));
426
+ if (wasPending) {
427
+ controller.abort(error.match({ just: (err) => err, nothing: () => undefined }));
428
+ }
429
+ return wasPending;
430
+ };
345
431
 
346
- try {
347
- resolve(Maybe.just(Either.right(producer())));
348
- } catch (e) {
349
- resolve(Maybe.just(Either.left(e)));
432
+ try {
433
+ producer(controller.signal).then((value) => {
434
+ cell.resolve(Maybe.just(Either.right(value)));
435
+ }, (error) => {
436
+ if (controller.signal.aborted && (error?.name === 'AbortError' || (typeof DOMException !== 'undefined' && error instanceof DOMException && error.name === 'AbortError'))) {
437
+ cell.resolve(Maybe.nothing());
438
+ } else {
439
+ cell.resolve(Maybe.just(Either.left(error)));
350
440
  }
351
- }),
352
- cancel,
353
- );
441
+ });
442
+ } catch (e) {
443
+ cell.resolve(Maybe.just(Either.left(e)));
444
+ }
445
+
446
+ return Task.create(cell.promise, cancel);
354
447
  }
355
448
 
356
449
  /**
@@ -431,21 +524,50 @@ export namespace Task {
431
524
  * ```
432
525
  */
433
526
  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
- );
527
+ const generator: TaskGenerator<T, TT, R> = taskGeneratorFunction();
528
+ const cell = settleCell<Cancelable<R>>();
529
+
530
+ let current: { task: Task<T>; error: Maybe<any> | null } | null = null;
531
+
532
+ const sequentor = (invoke: () => IteratorResult<TT, R>, override?: Cancelable<R>) => {
533
+ try {
534
+ const next = invoke();
535
+
536
+ if (next.done) {
537
+ cell.resolve(override ?? current?.error?.map(Either.left) ?? Maybe.just(Either.right(next.value)));
538
+ current = null;
539
+ return;
540
+ }
541
+
542
+ current = {
543
+ task: next.value,
544
+ error: null,
545
+ };
546
+
547
+ next.value._invoke((maybe) => maybe.matchTap({
548
+ just: (either) => either.matchTap({
549
+ right: (value) => sequentor(() => generator.next(value), override),
550
+ left: (error) => sequentor(() => generator.throw(error), override),
551
+ }),
552
+ nothing: () => sequentor(() => generator.return(undefined as R), Maybe.nothing()),
553
+ }))
554
+ } catch (error) {
555
+ cell.resolve(override ?? Maybe.just(Either.left(error)));
556
+ current = null;
557
+ }
446
558
  };
447
559
 
448
- return Task.resolved(undefined).chain(() => sequentor(generator.next()));
560
+ sequentor(() => generator.next());
561
+
562
+ return Task.create(cell.promise, (error) => {
563
+ if (cell.settled() || !current) {
564
+ return false;
565
+ }
566
+
567
+ current.error = error;
568
+
569
+ return current.task._cancel(error);
570
+ });
449
571
  }
450
572
 
451
573
  /**
@@ -460,9 +582,7 @@ export namespace Task {
460
582
  * @returns task resolving to generator's return type
461
583
  */
462
584
  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
- });
585
+ return (...args: A) => generate(() => taskGeneratorFunction(...args));
466
586
  }
467
587
 
468
588
  /**
@@ -483,46 +603,7 @@ export namespace Task {
483
603
  * ```
484
604
  */
485
605
  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
- );
606
+ return fromCallback<NodeJS.Timeout, void>((resolve) => setTimeout(resolve, delay), clearTimeout);
526
607
  }
527
608
 
528
609
  /**
@@ -553,44 +634,35 @@ export namespace Task {
553
634
  * @retuirns task resolving to the list of results
554
635
  */
555
636
  export function all<T>(tasks: Iterable<Task<T>>) {
556
- const [resolve, setResolve] = resolver<Cancelable<T[]>>();
557
-
558
637
  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
- });
638
+ if (taskArray.length === 0) {
639
+ return Task.resolved([]);
640
+ }
641
+
642
+ return Task.fromCallback<Task<T>[], T[]>((resolve, reject, cancel) => {
643
+ const results: T[] = new Array(taskArray.length);
644
+ let remaining = taskArray.length;
645
+
646
+ taskArray.forEach((task, i) => {
647
+ task.matchTap({
648
+ resolved: (value) => {
649
+ results[i] = value;
650
+ remaining--;
651
+ if (remaining === 0) resolve(results);
652
+ },
653
+ rejected: (error) => {
654
+ reject(error);
655
+ taskArray.forEach((t) => t.cancel());
656
+ },
657
+ canceled: () => {
658
+ cancel();
659
+ taskArray.forEach((t) => t.cancel());
660
+ },
590
661
  });
591
- }),
592
- cancel,
593
- );
662
+ });
663
+
664
+ return taskArray;
665
+ }, (subtasks) => subtasks.forEach((t) => t.cancel()));
594
666
  }
595
667
 
596
668
  /**
@@ -608,44 +680,40 @@ export namespace Task {
608
680
  * @retuirns task resolving to the result of first successfull task
609
681
  */
610
682
  export function any<T>(tasks: Iterable<Task<T>>) {
611
- const [resolve, setResolve] = resolver<Cancelable<T>>();
683
+ const cell = settleCell<Cancelable<T>>();
612
684
 
613
685
  const taskArray = Array.from(tasks);
614
686
 
615
- const cancel = (error: Maybe<any>) => taskArray.forEach((task) => task._cancel(error));
687
+ if (taskArray.length < 1) {
688
+ return Task.canceled();
689
+ }
690
+
691
+ const cancel = (error: Maybe<any>) => taskArray.map((task) => task._cancel(error)).every((r) => r);
616
692
 
617
- const list = taskArray.map<Maybe<any>>(Maybe.nothing);
693
+ const list = taskArray.map<Maybe<any>>(() => Maybe.nothing());
618
694
 
619
695
  const resolved = (value: Maybe<T>) => {
620
- resolve(value.map(Either.right));
696
+ cell.resolve(value.map(Either.right));
621
697
  cancel(Maybe.nothing());
622
698
  };
699
+
623
700
  const rejected = (error: any, i: number) => {
624
701
  list[i] = Maybe.just(error);
625
702
 
626
703
  if (Maybe.everyJust(list)) {
627
- resolve(Maybe.just(Either.left(list.map(Just.just))));
704
+ cell.resolve(Maybe.just(Either.left(list.map((v) => Just.just(v)))));
628
705
  }
629
706
  };
630
707
 
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
- }
708
+ taskArray.map((task, i) => {
709
+ return task.matchTap({
710
+ resolved: (value) => resolved(Maybe.just(value)),
711
+ rejected: (error) => rejected(error, i),
712
+ canceled: () => resolved(Maybe.nothing()),
713
+ });
714
+ });
638
715
 
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
- );
716
+ return Task.create(cell.promise, cancel);
649
717
  }
650
718
 
651
719
  /**
@@ -682,74 +750,116 @@ export namespace Task {
682
750
  /// --------------------------------------------------------------------------------------
683
751
 
684
752
  type TaskInvoke<R> = Promise<Cancelable<R>>;
685
- type TaskCancel = (error: Maybe<any>) => void;
753
+ type TaskCancel = (error: Maybe<any>) => boolean;
686
754
 
687
755
  /**
688
756
  * @hidden
689
757
  */
690
758
  interface TaskBase<R> {
691
- /**
692
- * @hidden
693
- */
694
- readonly _invoke: TaskInvoke<R>;
695
- /**
696
- * @hidden
697
- */
698
- readonly _cancel: TaskCancel;
759
+ _invoke(op: (result: Cancelable<R>) => void): void;
760
+ _cancel(error: Maybe<any>): boolean;
699
761
  }
700
762
 
701
- const resolver = <T>() => {
702
- const stub: (t: T) => void = () => void undefined;
703
-
704
- let globalResolve = stub;
705
-
706
- const resolve = (value: T) => {
707
- const r = globalResolve;
708
-
709
- globalResolve = stub;
710
-
711
- r(value);
712
- };
713
-
714
- const setResolve = (value: typeof resolve) => {
715
- globalResolve = value;
716
- };
717
-
718
- return [resolve, setResolve] as [typeof resolve, typeof setResolve];
763
+ /**
764
+ * One-shot settlement cell.
765
+ *
766
+ * The Promise is created internally, so there is no unarmed window: `resolve`
767
+ * before or after construction always owns the result (first writer wins).
768
+ * Later `resolve` calls are ignored. Reentrant `resolve` from the sink is ignored.
769
+ *
770
+ * `settled` is for skipping continuations; it does not cancel children by itself.
771
+ * After creating a child, if `settled` is already true, cancel that child with
772
+ * the stored value. Bind `_cancel` to the child only after that check.
773
+ */
774
+ type SettleCell<T> = {
775
+ readonly promise: Promise<T>;
776
+ readonly resolve: (value: T) => boolean;
777
+ readonly settled: () => { value: T } | null;
719
778
  };
720
779
 
721
- function chainTaskMaybe<R, R2>(_task: TaskBase<R>, op: (value: Cancelable<R>) => Task<R2>) {
722
- const globalCancel = { cancel: _task._cancel };
780
+ const stub = () => void undefined;
723
781
 
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);
782
+ const settleCell = <T>(): SettleCell<T> => {
783
+ let sink: (value: T) => void = stub;
784
+ let settled: { value: T } | null = null;
729
785
 
730
- globalCancel.cancel = cancel2;
786
+ const promise = new Promise<T>((resolve) => {
787
+ sink = resolve;
788
+ });
731
789
 
732
- const produced = await invoke2;
790
+ const resolve = (next: T) => {
791
+ if (settled) {
792
+ return false;
793
+ }
733
794
 
734
- if (produced.isNothing()) {
735
- return Maybe.nothing();
736
- }
795
+ settled = { value: next };
737
796
 
738
- if (produced.just.isLeft()) {
739
- return Maybe.just(Either.left(produced.just.left));
740
- }
797
+ sink(next);
741
798
 
742
- const { _invoke: invoke3, _cancel: cancel3 } = produced.just.right;
799
+ return true;
800
+ };
743
801
 
744
- globalCancel.cancel = cancel3;
802
+ return {
803
+ promise,
804
+ resolve,
805
+ settled: () => settled,
806
+ };
807
+ };
745
808
 
746
- return invoke3;
747
- } catch (e) {
748
- return Maybe.just(Either.left(e));
749
- }
750
- }),
751
- (error: Maybe<any>) => globalCancel.cancel(error),
752
- );
809
+ function chainTaskMaybe<R, R2>(parent: TaskBase<R>, op: (value: Cancelable<R>) => Task<R2>) {
810
+ const cell = settleCell<Cancelable<R2>>();
811
+
812
+ let state:
813
+ | { phase: 'parent' }
814
+ | { phase: 'transform', error: Maybe<any> | null }
815
+ | { phase: 'child', cancel: TaskCancel }
816
+ | { phase: 'closed' } = { phase: 'parent' };
817
+
818
+ parent._invoke((result) => {
819
+ state = { phase: 'transform', error: null }
820
+
821
+ let child: Task<R2> | null = null;
822
+ try {
823
+ child = op(result);
824
+ } catch (e) {
825
+ cell.resolve(Maybe.just(Either.left(e)));
826
+ state = { phase: 'closed' };
827
+ return;
828
+ }
829
+
830
+ const transformError = state.phase === 'transform' ? state.error : null;
831
+
832
+ state = { phase: 'child', cancel: (error) => child._cancel(error) };
833
+
834
+ if (transformError) {
835
+ cell.resolve(transformError.map(Either.left));
836
+ state = { phase: 'closed' };
837
+ child._cancel(transformError);
838
+ return;
839
+ }
840
+
841
+ return child._invoke((value) => {
842
+ cell.resolve(value);
843
+ state = { phase: 'closed' };
844
+ });
845
+ });
846
+
847
+ return Task.create(cell.promise, (error) => {
848
+ if (cell.settled()) {
849
+ return false;
850
+ }
851
+
852
+ if (state.phase === 'parent') {
853
+ return parent._cancel(error);
854
+ } else if (state.phase === 'transform') {
855
+ state.error = error;
856
+ return true;
857
+ } else if (state.phase === 'child') {
858
+ return state.cancel(error);
859
+ } else {
860
+ return false;
861
+ }
862
+ });
753
863
  }
754
864
 
755
865
  function chainTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) => Task<R2>) {
@@ -758,14 +868,14 @@ function chainTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) =
758
868
 
759
869
  function mapTaskMaybe<R, R2>(_task: TaskBase<R>, op: (value: Cancelable<R>) => Cancelable<R2>) {
760
870
  return chainTaskMaybe<R, R2>(_task, (maybe) => {
761
- return op(maybe).matchMap<Task<R2>>({
871
+ return op(maybe).match<Task<R2>>({
762
872
  just: (either) =>
763
- either.matchMap<Task<R2>>({
873
+ either.match<Task<R2>>({
764
874
  right: Task.resolved,
765
875
  left: Task.rejected,
766
- }).right,
876
+ }),
767
877
  nothing: Task.canceled,
768
- }).just;
878
+ });
769
879
  });
770
880
  }
771
881
 
@@ -785,8 +895,37 @@ function tapTaskEither<R>(_task: TaskBase<R>, op: (value: Rejectable<R>) => void
785
895
  return tapTaskMaybe<R>(_task, (maybe) => maybe.tap(op));
786
896
  }
787
897
 
898
+ type SyncState<R> =
899
+ | { resolved: false; invoke: TaskInvoke<R>; cancel: TaskCancel }
900
+ | { resolved: true; result: Cancelable<R> }
901
+
788
902
  class TaskClass<R> implements Task<R> {
789
- constructor(readonly _invoke: TaskInvoke<R>, readonly _cancel: TaskCancel) {}
903
+ constructor(private _state: SyncState<R>) {
904
+ if (!this._state.resolved) {
905
+ this._state.invoke = this._state.invoke.then((result) => {
906
+ if (!this._state.resolved) {
907
+ this._state = { resolved: true, result }
908
+ }
909
+ return result;
910
+ });
911
+ }
912
+ }
913
+
914
+ _invoke(op: (result: Cancelable<R>) => void) {
915
+ if (this._state.resolved) {
916
+ return op(this._state.result);
917
+ } else {
918
+ return this._state.invoke.then(op);
919
+ }
920
+ }
921
+
922
+ _cancel(error: Maybe<any>) {
923
+ if (this._state.resolved) {
924
+ return false
925
+ } else {
926
+ return this._state.cancel(error);
927
+ }
928
+ }
790
929
 
791
930
  tapCanceled(op: () => void) {
792
931
  return tapTaskMaybe<R>(this, (maybe) => maybe.orTap(op));
@@ -820,14 +959,34 @@ class TaskClass<R> implements Task<R> {
820
959
  return chainTaskEither<R, R2>(this, (either) => either.map(op).orMap<Task<R2>>(Task.rejected).right);
821
960
  }
822
961
 
962
+ resolved() {
963
+ if (this._state.resolved) {
964
+ return this._state.result;
965
+ } else {
966
+ return null;
967
+ }
968
+ }
969
+
823
970
  resolve() {
824
- return this._invoke;
971
+ if (this._state.resolved) {
972
+ return Promise.resolve(this._state.result);
973
+ } else {
974
+ return this._state.invoke;
975
+ }
825
976
  }
826
977
  cancel() {
827
- return this._cancel(Maybe.nothing());
978
+ if (this._state.resolved) {
979
+ return false;
980
+ } else {
981
+ return this._state.cancel(Maybe.nothing());
982
+ }
828
983
  }
829
984
  reject(error: any) {
830
- return this._cancel(Maybe.just(error));
985
+ if (this._state.resolved) {
986
+ return false;
987
+ } else {
988
+ return this._state.cancel(Maybe.just(error));
989
+ }
831
990
  }
832
991
 
833
992
  generator() {
@@ -852,29 +1011,29 @@ class TaskClass<R> implements Task<R> {
852
1011
 
853
1012
  matchMap<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => R2; rejected: (error: any) => R3; canceled: () => R4 }) {
854
1013
  return mapTaskMaybe(this, (maybe) => {
855
- return maybe.matchMap<Right<R2 | R3 | R4, any>>({
1014
+ return Maybe.just(maybe.match<Right<R2 | R3 | R4, any>>({
856
1015
  just: (either) => {
857
- return either.matchMap({
1016
+ return Either.right(either.match({
858
1017
  right: op.resolved,
859
1018
  left: op.rejected,
860
- });
1019
+ }));
861
1020
  },
862
1021
  nothing: () => Either.right(op.canceled()),
863
- });
1022
+ }));
864
1023
  });
865
1024
  }
866
1025
 
867
1026
  matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }) {
868
1027
  return chainTaskMaybe(this, (maybe) => {
869
- return maybe.matchMap<Task<R2 | R3 | R4>>({
1028
+ return maybe.match<Task<R2 | R3 | R4>>({
870
1029
  just: (either) => {
871
- return either.matchMap<Task<R2 | R3>>({
1030
+ return either.match<Task<R2 | R3>>({
872
1031
  right: op.resolved,
873
1032
  left: op.rejected,
874
- }).right;
1033
+ });
875
1034
  },
876
1035
  nothing: op.canceled,
877
- }).just;
1036
+ });
878
1037
  });
879
1038
  }
880
1039
  }