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/dist/t-tasks.d.ts +1790 -0
- package/dist/t-tasks.esm.js +752 -2
- package/dist/t-tasks.esm.js.map +1 -1
- package/dist/t-tasks.js +788 -2
- package/dist/t-tasks.js.map +1 -1
- package/package.json +12 -7
- package/src/either.ts +89 -56
- package/src/maybe.ts +83 -50
- package/src/task.ts +372 -213
- package/dist/either.d.ts +0 -626
- package/dist/index.d.ts +0 -3
- package/dist/maybe.d.ts +0 -595
- package/dist/t-tasks.modern.mjs +0 -2
- package/dist/t-tasks.modern.mjs.map +0 -1
- package/dist/t-tasks.umd.js +0 -2
- package/dist/t-tasks.umd.js.map +0 -1
- package/dist/task.d.ts +0 -489
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: () =>
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
299
|
+
const cell = settleCell<Cancelable<R>>();
|
|
298
300
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
683
|
+
const cell = settleCell<Cancelable<T>>();
|
|
612
684
|
|
|
613
685
|
const taskArray = Array.from(tasks);
|
|
614
686
|
|
|
615
|
-
|
|
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
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
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>) =>
|
|
753
|
+
type TaskCancel = (error: Maybe<any>) => boolean;
|
|
686
754
|
|
|
687
755
|
/**
|
|
688
756
|
* @hidden
|
|
689
757
|
*/
|
|
690
758
|
interface TaskBase<R> {
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
722
|
-
const globalCancel = { cancel: _task._cancel };
|
|
780
|
+
const stub = () => void undefined;
|
|
723
781
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
786
|
+
const promise = new Promise<T>((resolve) => {
|
|
787
|
+
sink = resolve;
|
|
788
|
+
});
|
|
731
789
|
|
|
732
|
-
|
|
790
|
+
const resolve = (next: T) => {
|
|
791
|
+
if (settled) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
733
794
|
|
|
734
|
-
|
|
735
|
-
return Maybe.nothing();
|
|
736
|
-
}
|
|
795
|
+
settled = { value: next };
|
|
737
796
|
|
|
738
|
-
|
|
739
|
-
return Maybe.just(Either.left(produced.just.left));
|
|
740
|
-
}
|
|
797
|
+
sink(next);
|
|
741
798
|
|
|
742
|
-
|
|
799
|
+
return true;
|
|
800
|
+
};
|
|
743
801
|
|
|
744
|
-
|
|
802
|
+
return {
|
|
803
|
+
promise,
|
|
804
|
+
resolve,
|
|
805
|
+
settled: () => settled,
|
|
806
|
+
};
|
|
807
|
+
};
|
|
745
808
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
|
|
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).
|
|
871
|
+
return op(maybe).match<Task<R2>>({
|
|
762
872
|
just: (either) =>
|
|
763
|
-
either.
|
|
873
|
+
either.match<Task<R2>>({
|
|
764
874
|
right: Task.resolved,
|
|
765
875
|
left: Task.rejected,
|
|
766
|
-
})
|
|
876
|
+
}),
|
|
767
877
|
nothing: Task.canceled,
|
|
768
|
-
})
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1014
|
+
return Maybe.just(maybe.match<Right<R2 | R3 | R4, any>>({
|
|
856
1015
|
just: (either) => {
|
|
857
|
-
return either.
|
|
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.
|
|
1028
|
+
return maybe.match<Task<R2 | R3 | R4>>({
|
|
870
1029
|
just: (either) => {
|
|
871
|
-
return either.
|
|
1030
|
+
return either.match<Task<R2 | R3>>({
|
|
872
1031
|
right: op.resolved,
|
|
873
1032
|
left: op.rejected,
|
|
874
|
-
})
|
|
1033
|
+
});
|
|
875
1034
|
},
|
|
876
1035
|
nothing: op.canceled,
|
|
877
|
-
})
|
|
1036
|
+
});
|
|
878
1037
|
});
|
|
879
1038
|
}
|
|
880
1039
|
}
|