| @@ -0,0 +1,41 | |||
|
|
1 | import { ICancellation, IDestroyable } from "./interfaces"; | |
|
|
2 | ||
|
|
3 | export class CancellationAggregate implements ICancellation { | |
|
|
4 | private readonly _tokens: ICancellation[]; | |
|
|
5 | ||
|
|
6 | constructor(tokens: ICancellation[]) { | |
|
|
7 | this._tokens = tokens || []; | |
|
|
8 | } | |
|
|
9 | ||
|
|
10 | throwIfRequested() { | |
|
|
11 | this._tokens.forEach(ct => ct.throwIfRequested()); | |
|
|
12 | } | |
|
|
13 | ||
|
|
14 | isRequested() { | |
|
|
15 | return this._tokens.some(ct => ct.isRequested()); | |
|
|
16 | } | |
|
|
17 | isSupported() { | |
|
|
18 | return !!this._tokens.length; | |
|
|
19 | } | |
|
|
20 | register(cb: (e: any) => void): IDestroyable { | |
|
|
21 | let fired = false; | |
|
|
22 | ||
|
|
23 | const once = (e: any) => { | |
|
|
24 | if (!fired) { | |
|
|
25 | fired = true; | |
|
|
26 | destroy(); | |
|
|
27 | cb(e); | |
|
|
28 | } | |
|
|
29 | } | |
|
|
30 | ||
|
|
31 | const destroy = () => subscriptions | |
|
|
32 | .splice(0,subscriptions.length) // empty array | |
|
|
33 | .forEach(subscription => subscription.destroy()); // cleanup | |
|
|
34 | ||
|
|
35 | const subscriptions = this._tokens.map(ct => ct.register(once)) | |
|
|
36 | ||
|
|
37 | return { | |
|
|
38 | destroy | |
|
|
39 | }; | |
|
|
40 | } | |
|
|
41 | } | |
| @@ -1,3 +1,4 | |||
|
|
1 | import { CancellationAggregate } from "./CancellationAggregate"; | |
|
|
1 | 2 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
|
2 | 3 | import { argumentNotNull, destroyed } from "./safe"; |
|
|
3 | 4 | |
| @@ -80,4 +81,26 export class Cancellation implements ICa | |||
|
|
80 | 81 | return destroyed; |
|
|
81 | 82 | } |
|
|
82 | 83 | }; |
|
|
84 | ||
|
|
85 | /** | |
|
|
86 | * Combines multiple cancellation tokens to the single aggregated token. | |
|
|
87 | * | |
|
|
88 | * Aggregated token will be considered as signalled when some tokens are | |
|
|
89 | * signalled. The cancellation callback can be registered with the `register` | |
|
|
90 | * method, it will be fired once with the first signalled token, all other | |
|
|
91 | * tokens will be ignored. | |
|
|
92 | * | |
|
|
93 | * The tokens which don't support cancellation are filtered out, if there are | |
|
|
94 | * no tokens left in the list the method returns `Cancellation.none`. | |
|
|
95 | * | |
|
|
96 | * @param args The list of cancellation tokens to combine | |
|
|
97 | * @returns | |
|
|
98 | */ | |
|
|
99 | static combine(...args: ICancellation[]) { | |
|
|
100 | const tokens = args.filter(ct => ct.isSupported()); | |
|
|
101 | return tokens.length > 1 ? | |
|
|
102 | new CancellationAggregate(tokens) : | |
|
|
103 | tokens.length == 1 ? tokens[0] : | |
|
|
104 | this.none; | |
|
|
83 | 105 | } |
|
|
106 | } | |
| @@ -1,6 +1,5 | |||
|
|
1 | 1 | import { Cancellation } from "../Cancellation"; |
|
|
2 |
import { IAsyncComponent, ICancellation, ICancellable |
|
|
|
3 | import { destroy } from "../safe"; | |
|
|
2 | import { IAsyncComponent, ICancellation, ICancellable } from "../interfaces"; | |
|
|
4 | 3 | |
|
|
5 | 4 | const noop = () => void (0); |
|
|
6 | 5 | |
| @@ -13,21 +12,16 export class AsyncComponent implements I | |||
|
|
13 | 12 | |
|
|
14 | 13 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { |
|
|
15 | 14 | // create inner cancellation bound to the passed cancellation token |
|
|
16 | let h: IDestroyable; | |
|
|
17 | 15 | const inner = new Cancellation(cancel => { |
|
|
18 | ||
|
|
19 | 16 | this._cancel = cancel; |
|
|
20 | h = ct.register(cancel); | |
|
|
21 | 17 | }); |
|
|
22 | 18 | |
|
|
23 | // TODO create cancellation source here | |
|
|
24 | 19 |
|
|
|
25 | 20 | try { |
|
|
26 |
|
|
|
|
21 | return op(Cancellation.combine(ct, inner)); | |
|
|
27 | 22 | } finally { |
|
|
28 | 23 | // after the operation is complete we need to cleanup the |
|
|
29 | 24 | // resources |
|
|
30 | destroy(h); | |
|
|
31 | 25 | this._cancel = noop; |
|
|
32 | 26 | } |
|
|
33 | 27 | }; |
| @@ -1,4 +1,5 | |||
|
|
1 | 1 | import { Cancellation } from "../Cancellation"; |
|
|
2 | import { CancellationAggregate } from "../CancellationAggregate"; | |
|
|
2 | 3 | import { delay, notImplemented } from "../safe"; |
|
|
3 | 4 | import { test } from "./TestTraits"; |
|
|
4 | 5 | |
| @@ -86,3 +87,20 test("operation normal flow", async t => | |||
|
|
86 | 87 | clearTimeout(htimeout); |
|
|
87 | 88 | } |
|
|
88 | 89 | }); |
|
|
90 | ||
|
|
91 | test("combine cancellations", t => { | |
|
|
92 | const ct1 = new Cancellation(cancel => { | |
|
|
93 | cancel("cancelled"); | |
|
|
94 | }); | |
|
|
95 | ||
|
|
96 | const ct2 = new Cancellation(() => {}); | |
|
|
97 | ||
|
|
98 | const cct1 = Cancellation.combine(Cancellation.none, ct1); | |
|
|
99 | ||
|
|
100 | t.equals(cct1, ct1, "Cancellation.combine should filter out Cancellation.none tokens"); | |
|
|
101 | ||
|
|
102 | const cct2 = Cancellation.combine(Cancellation.none, ct1, ct2); | |
|
|
103 | ||
|
|
104 | t.assert(cct2 instanceof CancellationAggregate, "Cancellation.combine should return CancellationAggregate"); | |
|
|
105 | t.true(cct2.isRequested(), "CancellationAggregate should return isRequested true if any of cancellations is requested"); | |
|
|
106 | }); | |
General Comments 0
You need to be logged in to leave comments.
Login now
