| @@ -0,0 +1,12 | |||||
|
|
1 | import { Cancellation } from "./Cancellation"; | |||
|
|
2 | import { ICancellation } from "./interfaces"; | |||
|
|
3 | ||||
|
|
4 | export class CancelledError extends Error { | |||
|
|
5 | readonly cancellationToken: ICancellation; | |||
|
|
6 | ||||
|
|
7 | constructor(message = "The operation is cancelled", ct = Cancellation.none) { | |||
|
|
8 | super(message); | |||
|
|
9 | this.cancellationToken = ct; | |||
|
|
10 | this.name = "CancelledError"; | |||
|
|
11 | } | |||
|
|
12 | } | |||
| @@ -1,4 +1,5 | |||||
| 1 | import { CancellationAggregate } from "./CancellationAggregate"; |
|
1 | import { CancellationAggregate } from "./CancellationAggregate"; | |
|
|
2 | import { CancelledError } from "./CancelledError"; | |||
| 2 | import { ICancellation, IDestroyable } from "./interfaces"; |
|
3 | import { ICancellation, IDestroyable } from "./interfaces"; | |
| 3 | import { argumentNotNull, destroyed } from "./safe"; |
|
4 | import { argumentNotNull, destroyed } from "./safe"; | |
| 4 |
|
5 | |||
| @@ -57,7 +58,7 export class Cancellation implements ICa | |||||
| 57 | if (this._reason) |
|
58 | if (this._reason) | |
| 58 | return; |
|
59 | return; | |
| 59 |
|
60 | |||
| 60 |
this._reason = (reason = reason || new Error( |
|
61 | this._reason = (reason = reason || new CancelledError(undefined, this)); | |
| 61 |
|
62 | |||
| 62 | if (this._cbs) { |
|
63 | if (this._cbs) { | |
| 63 | this._cbs.forEach(cb => cb(reason)); |
|
64 | this._cbs.forEach(cb => cb(reason)); | |
| @@ -94,13 +95,13 export class Cancellation implements ICa | |||||
| 94 | * no tokens left in the list the method returns `Cancellation.none`. |
|
95 | * no tokens left in the list the method returns `Cancellation.none`. | |
| 95 |
* |
|
96 | * | |
| 96 | * @param args The list of cancellation tokens to combine |
|
97 | * @param args The list of cancellation tokens to combine | |
| 97 | * @returns |
|
98 | * @returns Aggregated cancellation token | |
| 98 | */ |
|
99 | */ | |
| 99 | static combine(...args: ICancellation[]) { |
|
100 | static combine(...args: ICancellation[]) { | |
| 100 | const tokens = args.filter(ct => ct.isSupported()); |
|
101 | const tokens = args.filter(ct => ct.isSupported()); | |
| 101 | return tokens.length > 1 ? |
|
102 | return tokens.length > 1 ? | |
| 102 | new CancellationAggregate(tokens) : |
|
103 | new CancellationAggregate(tokens) : | |
| 103 | tokens.length == 1 ? tokens[0] : |
|
104 | tokens.length === 1 ? tokens[0] : | |
| 104 | this.none; |
|
105 | this.none; | |
| 105 | } |
|
106 | } | |
| 106 | } |
|
107 | } | |
| @@ -26,13 +26,13 export class CancellationAggregate imple | |||||
| 26 | destroy(); |
|
26 | destroy(); | |
| 27 | cb(e); |
|
27 | cb(e); | |
| 28 | } |
|
28 | } | |
| 29 | } |
|
29 | }; | |
| 30 |
|
30 | |||
| 31 | const destroy = () => subscriptions |
|
31 | const destroy = () => subscriptions | |
| 32 | .splice(0,subscriptions.length) // empty array |
|
32 | .splice(0, subscriptions.length) // empty array | |
| 33 | .forEach(subscription => subscription.destroy()); // cleanup |
|
33 | .forEach(subscription => subscription.destroy()); // cleanup | |
| 34 |
|
34 | |||
| 35 | const subscriptions = this._tokens.map(ct => ct.register(once)) |
|
35 | const subscriptions = this._tokens.map(ct => ct.register(once)); | |
| 36 |
|
36 | |||
| 37 | return { |
|
37 | return { | |
| 38 | destroy |
|
38 | destroy | |
| @@ -18,7 +18,10 export class AsyncComponent implements I | |||||
| 18 |
|
18 | |||
| 19 | const guard = async () => { |
|
19 | const guard = async () => { | |
| 20 | try { |
|
20 | try { | |
| 21 |
|
|
21 | const combined = Cancellation.combine(ct, inner); | |
|
|
22 | const result = await op(combined); | |||
|
|
23 | combined.throwIfRequested(); | |||
|
|
24 | return result; | |||
| 22 | } finally { |
|
25 | } finally { | |
| 23 | // after the operation is complete we need to cleanup the |
|
26 | // after the operation is complete we need to cleanup the | |
| 24 | // resources |
|
27 | // resources | |
| @@ -209,7 +209,7 export class LifetimeManager implements | |||||
| 209 | _lifetime.store(item); |
|
209 | _lifetime.store(item); | |
| 210 | }, |
|
210 | }, | |
| 211 | toString() { |
|
211 | toString() { | |
| 212 | return `[object ContainerLifetime, has=${_lifetime.has()}]` |
|
212 | return `[object ContainerLifetime, has=${_lifetime.has()}]`; | |
| 213 | } |
|
213 | } | |
| 214 | }; |
|
214 | }; | |
| 215 | } |
|
215 | } | |
| @@ -29,10 +29,48 export interface IRemovable { | |||||
| 29 | remove(): void; |
|
29 | remove(): void; | |
| 30 | } |
|
30 | } | |
| 31 |
|
31 | |||
|
|
32 | /** | |||
|
|
33 | * Interface for the cancellation token. Cancellation token is | |||
|
|
34 | * a marker indicating that the cancellation was requested, it | |||
|
|
35 | * is up to the operation to decide whether to interrupt or | |||
|
|
36 | * to complete its execution. | |||
|
|
37 | * | |||
|
|
38 | * This interface defines several methods of interaction with | |||
|
|
39 | * the cancellation i.e. either to poll its status or to react | |||
|
|
40 | * through the callback. | |||
|
|
41 | */ | |||
| 32 | export interface ICancellation { |
|
42 | export interface ICancellation { | |
|
|
43 | /** | |||
|
|
44 | * Throws an exception if the cancellation has been requested, | |||
|
|
45 | * otherwise does nothing. | |||
|
|
46 | */ | |||
| 33 | throwIfRequested(): void; |
|
47 | throwIfRequested(): void; | |
|
|
48 | ||||
|
|
49 | /** | |||
|
|
50 | * Checks whether the cancellation is requested. | |||
|
|
51 | * @returns true is the cancellation has been requested, | |||
|
|
52 | * otherwise returns false. | |||
|
|
53 | */ | |||
| 34 | isRequested(): boolean; |
|
54 | isRequested(): boolean; | |
|
|
55 | ||||
|
|
56 | /** | |||
|
|
57 | * Checks the ability of the token to be signaled. | |||
|
|
58 | * | |||
|
|
59 | * @returns true if the token is able to request | |||
|
|
60 | * the cancellation, false otherwise. | |||
|
|
61 | */ | |||
| 35 | isSupported(): boolean; |
|
62 | isSupported(): boolean; | |
|
|
63 | ||||
|
|
64 | /** | |||
|
|
65 | * Registers the callback to be called when the cancellation | |||
|
|
66 | * is requested. | |||
|
|
67 | * | |||
|
|
68 | * @param cb The callback which receives the reason of the | |||
|
|
69 | * cancellation. | |||
|
|
70 | * @returns The subscription, after the operation is completed | |||
|
|
71 | * it should unregister the callback to free resources by | |||
|
|
72 | * calling the `destroy()` method of the subscription. | |||
|
|
73 | */ | |||
| 36 | register(cb: (e: any) => void): IDestroyable; |
|
74 | register(cb: (e: any) => void): IDestroyable; | |
| 37 | } |
|
75 | } | |
| 38 |
|
76 | |||
| @@ -34,7 +34,7 test("Nested async configuration", async | |||||
| 34 | }); |
|
34 | }); | |
| 35 |
|
35 | |||
| 36 | t.assert(container.resolve("box").getValue(), "The dependency should be set"); |
|
36 | t.assert(container.resolve("box").getValue(), "The dependency should be set"); | |
| 37 | t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once") |
|
37 | t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once"); | |
| 38 | }); |
|
38 | }); | |
| 39 |
|
39 | |||
| 40 | test("Bad fluent config", async t => { |
|
40 | test("Bad fluent config", async t => { | |
General Comments 0
You need to be logged in to leave comments.
Login now
