UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

256 lines (225 loc) 7.78 kB
import { FDisposable } from "./f_disposable.js"; import { FExecutionContext } from "../execution_context/f_execution_context.js"; import { FException, FExceptionAggregate, FExceptionInvalidOperation } from "../exception/index.js"; import "./tc39.js"; export abstract class FInitable extends FDisposable { public abstract init(executionContext: FExecutionContext): Promise<void>; public static async initAll(executionContext: FExecutionContext, ...instances: ReadonlyArray<FInitable>): Promise<void> { const initializedInstances: Array<FInitable> = []; try { for (const instance of instances) { await instance.init(executionContext); initializedInstances.push(instance); } } catch (initEx) { const disposeExs: Array<FException> = []; for (const initializedInstance of initializedInstances.reverse()) { try { await initializedInstance.dispose(); } catch (disposeEx) { disposeExs.push(FException.wrapIfNeeded(disposeEx)); } if (disposeExs.length > 0) { throw new FExceptionAggregate([ FException.wrapIfNeeded(initEx), ...disposeExs ]); } } throw initEx; } } public static override instanceOf(test: unknown): test is FInitable { if (test instanceof FInitable) { return true; } if ( typeof test === "object" && test !== null // {} && "init" in test // { init: ... } && typeof test.init === "function" // { init: function(...) {} } && test.init.length === 1 // { init: function(probablyExecutionContext) {} } ) { return true; } return false; } } export abstract class FInitableBase extends FInitable { private _initialized?: boolean; private _initializingPromise?: Promise<void>; private _disposed?: boolean; private _disposingPromise?: Promise<void>; private _initExecutionContext: FExecutionContext | null; public get initialized(): boolean { return this._initialized === true; } public get initializing(): boolean { return this._initializingPromise !== undefined; } public get disposed(): boolean { return this._disposed === true; } public get disposing(): boolean { return this._disposingPromise !== undefined; } public constructor() { super(); this._initExecutionContext = null; } public init(executionContext: FExecutionContext): Promise<void> { this.verifyNotDisposed(); if (!this._initialized) { if (this._initializingPromise === undefined) { this._initExecutionContext = executionContext; this._initializingPromise = Promise.resolve(); const onInitializeResult = this.onInit(); if (onInitializeResult instanceof Promise) { this._initializingPromise = this._initializingPromise .then(() => onInitializeResult) .finally(() => { delete this._initializingPromise; this._initialized = true; }); return this._initializingPromise; } else { this._initialized = true; delete this._initializingPromise; } } else { return this._initializingPromise; } } return Promise.resolve(); } public async dispose(): Promise<void> { if (this._disposed !== true) { if (this._disposingPromise === undefined) { if (this._initializingPromise !== undefined) { this._disposingPromise = this._initializingPromise; this._disposingPromise = this._disposingPromise .then(async () => this.onDispose()) .finally(() => { delete this._disposingPromise; this._disposed = true; }); return this._disposingPromise; } else { this._disposingPromise = Promise.resolve(); if (this._initialized) { const onDisposeResult = this.onDispose(); if (onDisposeResult instanceof Promise) { this._disposingPromise = this._disposingPromise .then(() => onDisposeResult) .finally(() => { delete this._disposingPromise; this._disposed = true; }); return this._disposingPromise; } } this._disposed = true; delete this._disposingPromise; } } else { return this._disposingPromise; } } return Promise.resolve(); } /** * @remark Defined as property to be able to use inside dispose() */ protected get initExecutionContext(): FExecutionContext { if (!(this.initialized || this.initializing)) { throw new Error("Wrong operation. Cannot obtain initExecutionContext before call init()."); } return this._initExecutionContext!; } /** * Override this method to insert own logic at initialize phase * * Note: this.initExecutionContext may be used here */ protected abstract onInit(): void | Promise<void>; /** * Override this method to insert own logic at disposing phase * * Note: this.initExecutionContext may be used here */ protected abstract onDispose(): void | Promise<void>; protected verifyInitialized() { if (!this.initialized) { throw new Error("Wrong operation on non-initialized object"); } } protected verifyNotDisposed() { if (this.disposed || this.disposing) { throw new Error("Wrong operation on disposed object"); } } protected verifyInitializedAndNotDisposed() { this.verifyInitialized(); this.verifyNotDisposed(); } } export class FInitableMixin extends FInitableBase { public static applyMixin(targetClass: any): void { let sourceType = FInitableBase; while (sourceType.prototype !== undefined) { Object.getOwnPropertySymbols(sourceType.prototype).forEach(name => { const propertyDescriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(sourceType.prototype, name); if (propertyDescriptor !== undefined) { Object.defineProperty(targetClass.prototype, name, propertyDescriptor); } }); Object.getOwnPropertyNames(sourceType.prototype).forEach(name => { if (name === "constructor") { // Skip constructor return; } const propertyDescriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(sourceType.prototype, name); if (propertyDescriptor !== undefined) { Object.defineProperty(targetClass.prototype, name, propertyDescriptor); } }); sourceType = Object.getPrototypeOf(sourceType); } Object.getOwnPropertyNames(FInitableMixin.prototype).forEach(name => { if (name === "constructor") { // Skip constructor return; } const propertyDescriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(FInitableMixin.prototype, name); if (name === "onInit" || name === "onDispose") { // Add NOP methods into mixed only if it not implements its if (propertyDescriptor !== undefined) { const existingPropertyDescriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(targetClass.prototype, name); if (existingPropertyDescriptor === undefined) { Object.defineProperty(targetClass.prototype, name, propertyDescriptor); } } return; } if (propertyDescriptor !== undefined) { Object.defineProperty(targetClass.prototype, name, propertyDescriptor); } }); } /** * Override this method to insert own logic at initialize phase * * Note: this.initExecutionContext may be used here */ protected onInit(): void | Promise<void> { // Do nothing here by design. Users will override this method. } /** * Override this method to insert own logic at disposing phase * * Note: this.initExecutionContext may be used here */ protected onDispose(): void | Promise<void> { // Do nothing here by design. Users will override this method. } private constructor() { super(); // Never called, due mixin // Private constructor has two kinds of responsibility // 1) Restrict to extends the mixin // 2) Restrict to make instances of the mixin throw new FExceptionInvalidOperation("Private constructor"); } }