# HG changeset patch # User cin # Date 2021-05-19 21:27:26 # Node ID 3969a8fb80498737dd6149a1410b1d901915794e # Parent 2ad4aeec02ff0b80d7e9b722c848e94c68142a47 Added CancelledError, fixed lint warnings diff --git a/src/main/ts/Cancellation.ts b/src/main/ts/Cancellation.ts --- a/src/main/ts/Cancellation.ts +++ b/src/main/ts/Cancellation.ts @@ -1,4 +1,5 @@ import { CancellationAggregate } from "./CancellationAggregate"; +import { CancelledError } from "./CancelledError"; import { ICancellation, IDestroyable } from "./interfaces"; import { argumentNotNull, destroyed } from "./safe"; @@ -57,7 +58,7 @@ export class Cancellation implements ICa if (this._reason) return; - this._reason = (reason = reason || new Error("Operation cancelled")); + this._reason = (reason = reason || new CancelledError(undefined, this)); if (this._cbs) { this._cbs.forEach(cb => cb(reason)); @@ -84,23 +85,23 @@ export class Cancellation implements ICa /** * Combines multiple cancellation tokens to the single aggregated token. - * + * * Aggregated token will be considered as signalled when some tokens are * signalled. The cancellation callback can be registered with the `register` * method, it will be fired once with the first signalled token, all other * tokens will be ignored. - * + * * The tokens which don't support cancellation are filtered out, if there are * no tokens left in the list the method returns `Cancellation.none`. - * + * * @param args The list of cancellation tokens to combine - * @returns + * @returns Aggregated cancellation token */ static combine(...args: ICancellation[]) { const tokens = args.filter(ct => ct.isSupported()); return tokens.length > 1 ? new CancellationAggregate(tokens) : - tokens.length == 1 ? tokens[0] : + tokens.length === 1 ? tokens[0] : this.none; } } diff --git a/src/main/ts/CancellationAggregate.ts b/src/main/ts/CancellationAggregate.ts --- a/src/main/ts/CancellationAggregate.ts +++ b/src/main/ts/CancellationAggregate.ts @@ -26,14 +26,14 @@ export class CancellationAggregate imple destroy(); cb(e); } - } + }; const destroy = () => subscriptions - .splice(0,subscriptions.length) // empty array + .splice(0, subscriptions.length) // empty array .forEach(subscription => subscription.destroy()); // cleanup - const subscriptions = this._tokens.map(ct => ct.register(once)) - + const subscriptions = this._tokens.map(ct => ct.register(once)); + return { destroy }; diff --git a/src/main/ts/CancelledError.ts b/src/main/ts/CancelledError.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/CancelledError.ts @@ -0,0 +1,12 @@ +import { Cancellation } from "./Cancellation"; +import { ICancellation } from "./interfaces"; + +export class CancelledError extends Error { + readonly cancellationToken: ICancellation; + + constructor(message = "The operation is cancelled", ct = Cancellation.none) { + super(message); + this.cancellationToken = ct; + this.name = "CancelledError"; + } +} diff --git a/src/main/ts/components/AsyncComponent.ts b/src/main/ts/components/AsyncComponent.ts --- a/src/main/ts/components/AsyncComponent.ts +++ b/src/main/ts/components/AsyncComponent.ts @@ -18,7 +18,10 @@ export class AsyncComponent implements I const guard = async () => { try { - return op(Cancellation.combine(ct, inner)); + const combined = Cancellation.combine(ct, inner); + const result = await op(combined); + combined.throwIfRequested(); + return result; } finally { // after the operation is complete we need to cleanup the // resources diff --git a/src/main/ts/di/LifetimeManager.ts b/src/main/ts/di/LifetimeManager.ts --- a/src/main/ts/di/LifetimeManager.ts +++ b/src/main/ts/di/LifetimeManager.ts @@ -209,7 +209,7 @@ export class LifetimeManager implements _lifetime.store(item); }, toString() { - return `[object ContainerLifetime, has=${_lifetime.has()}]` + return `[object ContainerLifetime, has=${_lifetime.has()}]`; } }; } diff --git a/src/main/ts/interfaces.ts b/src/main/ts/interfaces.ts --- a/src/main/ts/interfaces.ts +++ b/src/main/ts/interfaces.ts @@ -29,10 +29,48 @@ export interface IRemovable { remove(): void; } +/** + * Interface for the cancellation token. Cancellation token is + * a marker indicating that the cancellation was requested, it + * is up to the operation to decide whether to interrupt or + * to complete its execution. + * + * This interface defines several methods of interaction with + * the cancellation i.e. either to poll its status or to react + * through the callback. + */ export interface ICancellation { + /** + * Throws an exception if the cancellation has been requested, + * otherwise does nothing. + */ throwIfRequested(): void; + + /** + * Checks whether the cancellation is requested. + * @returns true is the cancellation has been requested, + * otherwise returns false. + */ isRequested(): boolean; + + /** + * Checks the ability of the token to be signaled. + * + * @returns true if the token is able to request + * the cancellation, false otherwise. + */ isSupported(): boolean; + + /** + * Registers the callback to be called when the cancellation + * is requested. + * + * @param cb The callback which receives the reason of the + * cancellation. + * @returns The subscription, after the operation is completed + * it should unregister the callback to free resources by + * calling the `destroy()` method of the subscription. + */ register(cb: (e: any) => void): IDestroyable; } diff --git a/src/test/ts/tests/FluentContainerTests.ts b/src/test/ts/tests/FluentContainerTests.ts --- a/src/test/ts/tests/FluentContainerTests.ts +++ b/src/test/ts/tests/FluentContainerTests.ts @@ -34,7 +34,7 @@ test("Nested async configuration", async }); t.assert(container.resolve("box").getValue(), "The dependency should be set"); - t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once") + t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once"); }); test("Bad fluent config", async t => { @@ -71,7 +71,7 @@ test("Container applyConfig", async t => test("Child container config", async t => { const container = await new Container<{}>().applyConfig(import("../mock/config")); - + const fooServices: ContainerConfiguration = (await import("../mock/config2")).default; const child = await fooServices.apply(container.createChildContainer());