@freemework/common
Version:
Common library of the Freemework Project.
165 lines (136 loc) • 4.77 kB
text/typescript
import { FException, FExceptionAggregate, FExceptionInvalidOperation } from "../exception/index.js";
import "./tc39.js";
export abstract class FDisposable {
public async [Symbol.asyncDispose](): Promise<void> {
await this.dispose();
}
public abstract dispose(): Promise<void>;
public static async disposeAll(...instances: ReadonlyArray<FDisposable>): Promise<void> {
const innerExceptions: Array<FException> = [];
for (const instance of instances) {
try {
await instance.dispose();
} catch (e) {
innerExceptions.push(FException.wrapIfNeeded(e));
}
}
FExceptionAggregate.throwIfNeeded(innerExceptions);
}
public static instanceOf(test: unknown): test is FDisposable {
if (test instanceof FDisposable) {
return true;
}
if (
typeof test === "object"
&& test !== null // {}
&& "dispose" in test // { init: ... }
&& typeof test.dispose === "function" // { dispose: function(...) {} }
&& test.dispose.length === 0 // { dispose: function() {} }
) {
return true;
}
return false;
}
}
export abstract class FDisposableBase extends FDisposable {
private _disposed?: boolean;
private _disposingPromise?: Promise<void>;
public get disposed(): boolean { return this._disposed === true; }
public get disposing(): boolean { return this._disposingPromise !== undefined; }
public async dispose(): Promise<void> {
if (this._disposed !== true) {
if (this._disposingPromise === undefined) {
this._disposingPromise = Promise.resolve();
const onDisposeResult = this.onDispose();
if (onDisposeResult instanceof Promise) {
this._disposingPromise = this._disposingPromise
.then(() => onDisposeResult)
.finally(() => {
delete this._disposingPromise;
this._disposed = true;
});
return this._disposingPromise;
} else {
this._disposed = true;
delete this._disposingPromise;
}
} else {
return this._disposingPromise;
}
}
return Promise.resolve();
}
protected abstract onDispose(): void | Promise<void>;
protected verifyNotDisposed() {
if (this.disposed || this.disposing) {
throw new Error("Wrong operation on disposed object");
}
}
}
export class FDisposableMixin extends FDisposableBase {
public static applyMixin(targetClass: any): void {
let sourceType = FDisposableBase;
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(FDisposableMixin.prototype).forEach(name => {
if (name === "constructor") {
// Skip constructor
return;
}
const propertyDescriptor: PropertyDescriptor | undefined = Object.getOwnPropertyDescriptor(FDisposableMixin.prototype, name);
if (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);
}
});
}
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");
}
}
class FDisposableAdapter extends FDisposable {
public constructor(
private readonly _dispose: () => void | Promise<void>) {
super();
}
public override async dispose(): Promise<void> {
await this._dispose();
}
}
export function makeDisposable(dispose: () => void | Promise<void>): FDisposable {
return new FDisposableAdapter(dispose);
}