t-tasks 2.0.0 → 2.1.1

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,11 @@ 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
+ */
181
188
  resolved: () => Cancelable<R> | null;
182
189
 
183
190
  /**
@@ -188,14 +195,17 @@ export interface Task<R> extends TaskBase<R> {
188
195
  resolve: () => TaskInvoke<R>;
189
196
 
190
197
  /**
191
- * 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
192
201
  */
193
202
  cancel: () => boolean;
194
203
 
195
204
  /**
196
- * Invoke underlying canel method with error
205
+ * Invoke underlying cancel method with error
197
206
  *
198
207
  * @param error error value to be injected from outside
208
+ * @returns 'true' when the task was still pending and the error was applied
199
209
  */
200
210
  reject: (error: any) => boolean;
201
211
 
@@ -263,7 +273,7 @@ export namespace Task {
263
273
  *
264
274
  * @template R returned task's resolve type
265
275
  * @param error error to be returned upon awaiting
266
- * @returns task resolving to specified value
276
+ * @returns task rejected with the specified error
267
277
  */
268
278
  export function rejected<R>(error: any): Task<R> {
269
279
  return new TaskClass<R>({ resolved: true, result: Maybe.just(Either.left(error)) });
@@ -273,7 +283,7 @@ export namespace Task {
273
283
  * Invariant task constructor creating canceled task
274
284
  *
275
285
  * @template R returned task's resolve type
276
- * @returns task resolving to specified value
286
+ * @returns canceled task
277
287
  */
278
288
  export function canceled<R>(): Task<R> {
279
289
  return new TaskClass<R>({ resolved: true, result: Maybe.nothing() });
@@ -350,10 +360,12 @@ export namespace Task {
350
360
  /**
351
361
  * Generic callback task
352
362
  *
353
- * Usefull for creating custom tasks from success and error callbacks and cancelaton function. Synchronous callbacks are not supported, @see fromFuction
363
+ * Usefull for creating custom tasks from success and error callbacks and cancelaton function. Synchronous callbacks are not supported, @see fromFunction
354
364
  *
365
+ * @template H cancelation handler type
366
+ * @template R returned task resolve type
355
367
  * @param create task body with success and error callbacks
356
- * @param cancel cancelation function intended for stopping task execution
368
+ * @param onCancel cancelation function intended for stopping task execution
357
369
  * @returns task resolving to success value
358
370
  *
359
371
  * @example
@@ -460,9 +472,8 @@ export namespace Task {
460
472
  /**
461
473
  * Convinience shortcut for yielding timers
462
474
  *
463
- * @template R returned generator resolve type
464
- * @param promise promise to be resolved
465
- * @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*
466
477
  */
467
478
  export function timeoutGenerator(delay: number) {
468
479
  return Task.timeout(delay).generator();
@@ -500,6 +511,7 @@ export namespace Task {
500
511
  *
501
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
502
513
  *
514
+ * @template T yielded underlying type
503
515
  * @template TT yielded task type
504
516
  * @template R returned task resolve type
505
517
  * @param taskGeneratorFunction task generator function
@@ -575,11 +587,12 @@ export namespace Task {
575
587
  *
576
588
  * Simplifies wrighting functions returning generated tasks
577
589
  *
578
- * @template Args generator arguments
590
+ * @template A generator arguments
591
+ * @template T yielded underlying type
579
592
  * @template TT yielded task type
580
593
  * @template R returned task resolve type
581
594
  * @param taskGeneratorFunction function* (...args) task generator function
582
- * @returns task resolving to generator's return type
595
+ * @returns task function resolving to generator's return type
583
596
  */
584
597
  export function generateFunction<A extends any[], T, TT extends Task<T>, R>(taskGeneratorFunction: TaskGeneratorFunction<A, T, TT, R>): TaskFunction<A, R> {
585
598
  return (...args: A) => generate(() => taskGeneratorFunction(...args));
@@ -609,6 +622,7 @@ export namespace Task {
609
622
  /**
610
623
  * Start multiple tasks one after another
611
624
  *
625
+ * @template T underlying task type
612
626
  * @param taskFunctions list of task functions (without arguments)
613
627
  * @returns composite task invoring every task in order and resolving to the list of results
614
628
  */
@@ -631,9 +645,9 @@ export namespace Task {
631
645
  *
632
646
  * @template T underlying task type
633
647
  * @param tasks iterable of tasks to execute
634
- * @retuirns task resolving to the list of results
648
+ * @returns task resolving to the list of results
635
649
  */
636
- export function all<T>(tasks: Iterable<Task<T>>) {
650
+ export function all<T>(tasks: Iterable<Task<T>>): Task<T[]> {
637
651
  const taskArray = Array.from(tasks);
638
652
  if (taskArray.length === 0) {
639
653
  return Task.resolved([]);
@@ -677,43 +691,48 @@ export namespace Task {
677
691
  *
678
692
  * @template T underlying task type
679
693
  * @param tasks iterable of tasks to execute
680
- * @retuirns task resolving to the result of first successfull task
694
+ * @returns task resolving to the result of first successfull task
681
695
  */
682
- export function any<T>(tasks: Iterable<Task<T>>) {
683
- const cell = settleCell<Cancelable<T>>();
684
-
696
+ export function any<T>(tasks: Iterable<Task<T>>): Task<T> {
685
697
  const taskArray = Array.from(tasks);
686
698
 
687
- if (taskArray.length < 1) {
688
- return Task.canceled();
699
+ if (taskArray.length === 0) {
700
+ return Task.rejected([]);
689
701
  }
690
702
 
691
- const cancel = (error: Maybe<any>) => taskArray.map((task) => task._cancel(error)).every((r) => r);
692
-
693
- const list = taskArray.map<Maybe<any>>(() => Maybe.nothing());
694
-
695
- const resolved = (value: Maybe<T>) => {
696
- cell.resolve(value.map(Either.right));
697
- cancel(Maybe.nothing());
698
- };
699
-
700
- const rejected = (error: any, i: number) => {
701
- list[i] = Maybe.just(error);
703
+ return Task.fromCallback<Task<T>[], T>((resolve, reject, cancel) => {
704
+ const errors: Maybe<any>[] = taskArray.map(() => Maybe.nothing());
705
+ let remaining = taskArray.length;
702
706
 
703
- if (Maybe.everyJust(list)) {
704
- cell.resolve(Maybe.just(Either.left(list.map((v) => Just.just(v)))));
705
- }
706
- };
707
+ const checkAllFailed = () => {
708
+ remaining--;
709
+ if (remaining === 0) {
710
+ const rawErrors = errors.map((e) =>
711
+ e.match({ just: (err) => err, nothing: () => undefined })
712
+ );
713
+ reject(rawErrors);
714
+ }
715
+ };
707
716
 
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()),
717
+ taskArray.forEach((task, i) => {
718
+ task.matchTap({
719
+ resolved: (value) => {
720
+ resolve(value);
721
+ taskArray.forEach((t) => t.cancel());
722
+ },
723
+ rejected: (error) => {
724
+ errors[i] = Maybe.just(error);
725
+ checkAllFailed();
726
+ },
727
+ canceled: () => {
728
+ cancel();
729
+ taskArray.forEach((t) => t.cancel());
730
+ },
731
+ });
713
732
  });
714
- });
715
733
 
716
- return Task.create(cell.promise, cancel);
734
+ return taskArray;
735
+ }, (subtasks) => subtasks.forEach((t) => t.cancel()));
717
736
  }
718
737
 
719
738
  /**
@@ -722,12 +741,13 @@ export namespace Task {
722
741
  * If the second task resolves sooner than the first, resulting task is canceled
723
742
  * Otherwise the resulting task resolves together with the first one
724
743
  *
744
+ * @template T underlying task type
725
745
  * @param task task to limit
726
746
  * @param limitTask limiting task
727
747
  * @returns task limites according to limitTask
728
748
  */
729
749
  export function limit<T>(task: Task<T>, limitTask: Task<void>) {
730
- return Task.any<T>([task, limitTask.chain<T>(Task.canceled)]).chainRejected((errors) => Task.rejected<T>(errors[0]));
750
+ return Task.any<T>([task, limitTask.chain<T>(Task.canceled)]);
731
751
  }
732
752
 
733
753
  /**
@@ -736,7 +756,9 @@ export namespace Task {
736
756
  * Given task is re-generated in case of failure (both external and internal)
737
757
  * Resulting task is still cancelable by standard means
738
758
  *
759
+ * @template T underlying task type
739
760
  * @param taskFunction task preoducing function
761
+ * @returns task that retries until the produced task resolves
740
762
  */
741
763
  export function repeat<T>(taskFunction: TaskFunction<[], T>) {
742
764
  const sequentor = (): Task<T> => taskFunction().chainRejected(sequentor);
@@ -806,7 +828,7 @@ const settleCell = <T>(): SettleCell<T> => {
806
828
  };
807
829
  };
808
830
 
809
- function chainTaskMaybe<R, R2>(parent: TaskBase<R>, op: (value: Cancelable<R>) => Task<R2>) {
831
+ function chainTaskMaybe<R, R2>(parent: Task<R>, op: (value: Cancelable<R>) => Task<R2>): Task<R2> {
810
832
  const cell = settleCell<Cancelable<R2>>();
811
833
 
812
834
  let state:
@@ -862,36 +884,74 @@ function chainTaskMaybe<R, R2>(parent: TaskBase<R>, op: (value: Cancelable<R>) =
862
884
  });
863
885
  }
864
886
 
865
- function chainTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) => Task<R2>) {
866
- return chainTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op).orMap<Task<R2>>(Task.canceled).just);
867
- }
887
+ function mapTaskMaybe<R, R2>(parent: Task<R>, op: (value: Cancelable<R>) => Cancelable<R2>): Task<R2> {
888
+ const cell = settleCell<Cancelable<R2>>();
868
889
 
869
- function mapTaskMaybe<R, R2>(_task: TaskBase<R>, op: (value: Cancelable<R>) => Cancelable<R2>) {
870
- return chainTaskMaybe<R, R2>(_task, (maybe) => {
871
- return op(maybe).match<Task<R2>>({
872
- just: (either) =>
873
- either.match<Task<R2>>({
874
- right: Task.resolved,
875
- left: Task.rejected,
876
- }),
877
- nothing: Task.canceled,
878
- });
890
+ let state:
891
+ | { phase: 'parent' }
892
+ | { phase: 'transform', error: Maybe<any> | null }
893
+ | { phase: 'closed' } = { phase: 'parent' };
894
+
895
+ parent._invoke((result) => {
896
+ state = { phase: 'transform', error: null }
897
+
898
+ let value: Cancelable<R2> | null = null;
899
+ try {
900
+ value = op(result);
901
+ } catch (e) {
902
+ cell.resolve(Maybe.just(Either.left(e)));
903
+ state = { phase: 'closed' };
904
+ return;
905
+ }
906
+
907
+ const transformError = state.phase === 'transform' ? state.error : null;
908
+
909
+ if (transformError) {
910
+ cell.resolve(transformError.map(Either.left));
911
+ } else {
912
+ cell.resolve(value);
913
+ }
914
+
915
+ state = { phase: 'closed' };
916
+ });
917
+
918
+ return Task.create(cell.promise, (error) => {
919
+ if (cell.settled()) {
920
+ return false;
921
+ }
922
+
923
+ if (state.phase === 'parent') {
924
+ return parent._cancel(error);
925
+ } else if (state.phase === 'transform') {
926
+ state.error = error;
927
+ return true;
928
+ } else {
929
+ return false;
930
+ }
879
931
  });
880
932
  }
881
933
 
882
- function mapTaskEither<R, R2>(_task: TaskBase<R>, op: (value: Rejectable<R>) => Rejectable<R2>) {
883
- return mapTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op));
934
+ function tapTaskMaybe<R>(parent: Task<R>, op: (value: Cancelable<R>) => void): Task<R> {
935
+ parent._invoke((result) => {
936
+ try {
937
+ op(result);
938
+ } catch (e) {
939
+ reportError(e);
940
+ }
941
+ });
942
+
943
+ return parent;
884
944
  }
885
945
 
886
- function tapTaskMaybe<R>(_task: TaskBase<R>, op: (value: Cancelable<R>) => void) {
887
- return mapTaskMaybe<R, R>(_task, (value) => {
888
- op(value);
946
+ function chainTaskEither<R, R2>(_task: Task<R>, op: (value: Rejectable<R>) => Task<R2>): Task<R2> {
947
+ return chainTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op).orMap<Task<R2>>(Task.canceled).just);
948
+ }
889
949
 
890
- return value;
891
- });
950
+ function mapTaskEither<R, R2>(_task: Task<R>, op: (value: Rejectable<R>) => Rejectable<R2>): Task<R2> {
951
+ return mapTaskMaybe<R, R2>(_task, (maybe) => maybe.map(op));
892
952
  }
893
953
 
894
- function tapTaskEither<R>(_task: TaskBase<R>, op: (value: Rejectable<R>) => void) {
954
+ function tapTaskEither<R>(_task: Task<R>, op: (value: Rejectable<R>) => void): Task<R> {
895
955
  return tapTaskMaybe<R>(_task, (maybe) => maybe.tap(op));
896
956
  }
897
957
 
@@ -927,35 +987,35 @@ class TaskClass<R> implements Task<R> {
927
987
  }
928
988
  }
929
989
 
930
- tapCanceled(op: () => void) {
990
+ tapCanceled(op: () => void): Task<R> {
931
991
  return tapTaskMaybe<R>(this, (maybe) => maybe.orTap(op));
932
992
  }
933
- tapRejected(op: (error: any) => void) {
993
+ tapRejected(op: (error: any) => void): Task<R> {
934
994
  return tapTaskEither<R>(this, (either) => either.orTap(op));
935
995
  }
936
- tap(op: (value: R) => void) {
996
+ tap(op: (value: R) => void): Task<R> {
937
997
  return tapTaskEither<R>(this, (either) => either.tap(op));
938
998
  }
939
999
 
940
- mapCanceled<R2>(op: () => R2) {
1000
+ mapCanceled<R2>(op: () => R2): Task<R | R2> {
941
1001
  return mapTaskMaybe<R, R | R2>(this, (maybe) => maybe.orMap(() => Either.right(op())));
942
1002
  }
943
- mapRejected<R2>(op: (error: any) => R2) {
1003
+ mapRejected<R2>(op: (error: any) => R2): Task<R | R2> {
944
1004
  return mapTaskEither<R, R | R2>(this, (either) => either.orMap(op));
945
1005
  }
946
- map<R2>(op: (value: R) => R2) {
1006
+ map<R2>(op: (value: R) => R2): Task<R2> {
947
1007
  return mapTaskEither<R, R2>(this, (either) => either.map(op));
948
1008
  }
949
1009
 
950
- chainCanceled<R2>(op: () => Task<R2>) {
1010
+ chainCanceled<R2>(op: () => Task<R2>): Task<R | R2> {
951
1011
  const mapEither = (either: Rejectable<R>) => either.map(Task.resolved).orMap<Task<R>>(Task.rejected).right;
952
1012
 
953
1013
  return chainTaskMaybe<R, R | R2>(this, (maybe) => maybe.map(mapEither).orMap(op).just);
954
1014
  }
955
- chainRejected<R2>(op: (error: any) => Task<R2>) {
1015
+ chainRejected<R2>(op: (error: any) => Task<R2>): Task<R | R2> {
956
1016
  return chainTaskEither<R, R | R2>(this, (either) => either.map(Task.resolved).orMap(op).right);
957
1017
  }
958
- chain<R2>(op: (value: R) => Task<R2>) {
1018
+ chain<R2>(op: (value: R) => Task<R2>): Task<R2> {
959
1019
  return chainTaskEither<R, R2>(this, (either) => either.map(op).orMap<Task<R2>>(Task.rejected).right);
960
1020
  }
961
1021
 
@@ -995,7 +1055,7 @@ class TaskClass<R> implements Task<R> {
995
1055
  })(this);
996
1056
  }
997
1057
 
998
- matchTap(op: { resolved: (value: R) => void; rejected: (error: any) => void; canceled: () => void }) {
1058
+ matchTap(op: { resolved: (value: R) => void; rejected: (error: any) => void; canceled: () => void }): Task<R> {
999
1059
  return tapTaskMaybe(this, (maybe) => {
1000
1060
  return maybe.matchTap({
1001
1061
  just: (either) => {
@@ -1009,7 +1069,7 @@ class TaskClass<R> implements Task<R> {
1009
1069
  });
1010
1070
  }
1011
1071
 
1012
- matchMap<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => R2; rejected: (error: any) => R3; canceled: () => R4 }) {
1072
+ matchMap<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => R2; rejected: (error: any) => R3; canceled: () => R4 }): Task<R2 | R3 | R4> {
1013
1073
  return mapTaskMaybe(this, (maybe) => {
1014
1074
  return Maybe.just(maybe.match<Right<R2 | R3 | R4, any>>({
1015
1075
  just: (either) => {
@@ -1023,7 +1083,7 @@ class TaskClass<R> implements Task<R> {
1023
1083
  });
1024
1084
  }
1025
1085
 
1026
- matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }) {
1086
+ matchChain<R2, R3 = R2, R4 = R3>(op: { resolved: (value: R) => Task<R2>; rejected: (error: any) => Task<R3>; canceled: () => Task<R4> }): Task<R2 | R3 | R4> {
1027
1087
  return chainTaskMaybe(this, (maybe) => {
1028
1088
  return maybe.match<Task<R2 | R3 | R4>>({
1029
1089
  just: (either) => {
@@ -1037,3 +1097,9 @@ class TaskClass<R> implements Task<R> {
1037
1097
  });
1038
1098
  }
1039
1099
  }
1100
+
1101
+ function reportError(error: unknown): void {
1102
+ if (typeof globalThis.reportError === 'function') {
1103
+ globalThis.reportError(error);
1104
+ }
1105
+ }