UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

217 lines 8.5 kB
import { FDisposable } from "./f_disposable.js"; import { FException, FExceptionAggregate, FExceptionInvalidOperation } from "../exception/index.js"; import "./tc39.js"; export class FInitable extends FDisposable { static async initAll(executionContext, ...instances) { const initializedInstances = []; try { for (const instance of instances) { await instance.init(executionContext); initializedInstances.push(instance); } } catch (initEx) { const disposeExs = []; 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; } } static instanceOf(test) { 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 class FInitableBase extends FInitable { _initialized; _initializingPromise; _disposed; _disposingPromise; _initExecutionContext; get initialized() { return this._initialized === true; } get initializing() { return this._initializingPromise !== undefined; } get disposed() { return this._disposed === true; } get disposing() { return this._disposingPromise !== undefined; } constructor() { super(); this._initExecutionContext = null; } init(executionContext) { 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(); } async dispose() { 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() */ get initExecutionContext() { if (!(this.initialized || this.initializing)) { throw new Error("Wrong operation. Cannot obtain initExecutionContext before call init()."); } return this._initExecutionContext; } verifyInitialized() { if (!this.initialized) { throw new Error("Wrong operation on non-initialized object"); } } verifyNotDisposed() { if (this.disposed || this.disposing) { throw new Error("Wrong operation on disposed object"); } } verifyInitializedAndNotDisposed() { this.verifyInitialized(); this.verifyNotDisposed(); } } export class FInitableMixin extends FInitableBase { static applyMixin(targetClass) { let sourceType = FInitableBase; while (sourceType.prototype !== undefined) { Object.getOwnPropertySymbols(sourceType.prototype).forEach(name => { const propertyDescriptor = 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 = 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 = 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 = 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 */ onInit() { // 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 */ onDispose() { // Do nothing here by design. Users will override this method. } 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"); } } //# sourceMappingURL=f_initable.js.map