| @@ -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 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
2 | import { ICancellation, IDestroyable } from "./interfaces"; | |
| 2 | import { argumentNotNull, destroyed } from "./safe"; |
|
3 | import { argumentNotNull, destroyed } from "./safe"; | |
| 3 |
|
4 | |||
| @@ -80,4 +81,26 export class Cancellation implements ICa | |||||
| 80 | return destroyed; |
|
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; | |||
|
|
105 | } | |||
| 83 | } |
|
106 | } | |
| @@ -1,6 +1,5 | |||||
| 1 | import { Cancellation } from "../Cancellation"; |
|
1 | import { Cancellation } from "../Cancellation"; | |
| 2 |
import { IAsyncComponent, ICancellation, ICancellable |
|
2 | import { IAsyncComponent, ICancellation, ICancellable } from "../interfaces"; | |
| 3 | import { destroy } from "../safe"; |
|
|||
| 4 |
|
3 | |||
| 5 | const noop = () => void (0); |
|
4 | const noop = () => void (0); | |
| 6 |
|
5 | |||
| @@ -13,21 +12,16 export class AsyncComponent implements I | |||||
| 13 |
|
12 | |||
| 14 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { |
|
13 | runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { | |
| 15 | // create inner cancellation bound to the passed cancellation token |
|
14 | // create inner cancellation bound to the passed cancellation token | |
| 16 | let h: IDestroyable; |
|
|||
| 17 | const inner = new Cancellation(cancel => { |
|
15 | const inner = new Cancellation(cancel => { | |
| 18 |
|
||||
| 19 | this._cancel = cancel; |
|
16 | this._cancel = cancel; | |
| 20 | h = ct.register(cancel); |
|
|||
| 21 | }); |
|
17 | }); | |
| 22 |
|
18 | |||
| 23 | // TODO create cancellation source here |
|
|||
| 24 |
|
|
19 | const guard = async () => { | |
| 25 | try { |
|
20 | try { | |
| 26 |
|
|
21 | return op(Cancellation.combine(ct, inner)); | |
| 27 | } finally { |
|
22 | } finally { | |
| 28 | // after the operation is complete we need to cleanup the |
|
23 | // after the operation is complete we need to cleanup the | |
| 29 | // resources |
|
24 | // resources | |
| 30 | destroy(h); |
|
|||
| 31 | this._cancel = noop; |
|
25 | this._cancel = noop; | |
| 32 | } |
|
26 | } | |
| 33 | }; |
|
27 | }; | |
| @@ -1,4 +1,5 | |||||
| 1 | import { Cancellation } from "../Cancellation"; |
|
1 | import { Cancellation } from "../Cancellation"; | |
|
|
2 | import { CancellationAggregate } from "../CancellationAggregate"; | |||
| 2 | import { delay, notImplemented } from "../safe"; |
|
3 | import { delay, notImplemented } from "../safe"; | |
| 3 | import { test } from "./TestTraits"; |
|
4 | import { test } from "./TestTraits"; | |
| 4 |
|
5 | |||
| @@ -86,3 +87,20 test("operation normal flow", async t => | |||||
| 86 | clearTimeout(htimeout); |
|
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
