Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active September 30, 2021 07:41
Show Gist options
  • Save CMCDragonkai/1dbf5069d9efc11585c27cc774271584 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/1dbf5069d9efc11585c27cc774271584 to your computer and use it in GitHub Desktop.
Asynchronous Initialisation and Deinitialisation for JavaScript Classes #typescript #javascript
/**
* Use this when object lifetime matches object "readiness"
*/
class X {
protected _destroyed: boolean = false;
public static async createX(): Promise<X> {
return new X;
}
protected constructor() {
}
get destroyed(): boolean {
return this._destroyed;
}
public async destroy(): Promise<void> {
try {
if (this._destroyed) {
return;
}
this._destroyed = true;
} catch (e) {
this._destroyed = false;
throw e;
}
}
public async doSomething(): Promise<void> {
if (this._destroyed) {
throw new Error('X is destroyed');
}
console.log('X did something');
}
}
class Y extends X {
public static async createY(): Promise<Y> {
return new Y;
}
protected constructor() {
super();
}
public async destroy(): Promise<void> {
await super.destroy();
}
}
async function main() {
const x = await X.createX();
await x.destroy();
// once destroyed you must never use it
}
/**
* X is a create & destroy pattern
* This uses composition pattern, not inheritance
*/
class X {
/**
* Public to indicate non-encapsulation of Y
*/
public y: Y;
/**
* Protected to indicate Z it is encapsulated and managed by X
*/
protected z: Z;
protected _destroyed: boolean = false;
/**
* Y is a dependency but not encapsulated
* Z is an optional and overridable encapsulated dependency
* Default parameters are always put in the createX, not in the constructor
*/
public static async createX({
y,
z
}: {
y: Y,
z?: Z
}): Promise<X> {
z = z ?? await Z.createZ();
const x = new X({ y, z });
return x;
}
/**
* It is assumed that Y and Z are ready to be used
*/
protected constructor({ y, z }: { y: Y, z: Z }) {
this.y = y;
this.z = z;
}
get destroyed() {
return this._destroyed;
}
public async destroy(): Promise<void> {
try {
if (this._destroyed) {
return;
}
this._destroyed = true;
// Z is managed by X therefore it should destroy it
await this.z.destroy();
} catch (e) {
this._destroyed = false;
throw e;
}
}
public async useYZ(): Promise<void> {
if (this._destroyed) {
throw new Error('Already destroyed!');
}
await this.y.doSomething();
await this.z.doSomething();
}
}
class Y {
protected _destroyed: boolean = false;
public static async createY() {
const y = new Y;
return y;
}
protected constructor() {
}
public async destroy(): Promise<void> {
try {
if (this._destroyed) {
return;
}
this._destroyed = true;
} catch (e) {
this._destroyed = false;
throw e;
}
}
public async doSomething(): Promise<void> {
if (this._destroyed) {
throw new Error('Y is destroyed');
}
console.log('Something from Y');
}
}
class Z {
protected _destroyed: boolean = false;
public static async createZ(): Promise<Z> {
const z = new Z;
return z;
}
protected constructor() {
}
public async destroy(): Promise<void> {
try {
if (this._destroyed) {
return;
}
this._destroyed = true;
} catch (e) {
this._destroyed = false;
throw e;
}
}
public async doSomething(): Promise<void> {
if (this._destroyed) {
throw new Error('Z is destroyed');
}
console.log('Something from Z');
}
}
async function main() {
const y = await Y.createY();
const x = await X.createX({
y
});
await x.useYZ();
}
/**
* Use this when object lifetime outlives object "readiness"
* This means there is some use of the object when the object isn't running
*/
class X {
protected _running: boolean = false;
public constructor() {
}
get running(): boolean {
return this._running;
}
public async start(): Promise<void> {
try {
if (this._running) {
return;
}
this._running = true;
} catch (e) {
this._running = false;
throw e;
}
}
public async stop() {
try {
if (!this._running) {
return;
}
this._running = false;
} catch (e) {
this._running = true;
throw e;
}
}
public async doSomething() {
if (!this._running) {
throw new Error('X is not running');
}
console.log('X did something');
}
}
class Y extends X {
public constructor() {
super();
}
public async start() {
await super.start();
}
public async stop() {
await super.stop();
}
}
async function main() {
const x = new X;
await x.start();
await x.stop();
await x.start();
await x.stop();
}
/**
* X is a start & stop pattern
* This uses composition pattern, not inheritance
*/
class X {
/**
* Public to indicate non-encapsulation of Y
*/
public y: Y;
/**
* Protected to indicate Z it is encapsulated and managed by X
*/
protected z: Z;
protected _running: boolean = false;
/**
* Y is a dependency but not encapsulated
* Z is an optional and overridable encapsulated dependency
*/
public constructor({
y,
z = new Z
}: {
y: Y,
z?: Z
}) {
this.y = y;
this.z = z;
}
get running(): boolean {
return this._running;
}
/**
* It is assumed that Y is already started
* This will start Z because Z is encapsulated
*/
public async start(): Promise<void> {
try {
if (this._running) {
return;
}
this._running = true;
await this.z.start();
} catch (e) {
this._running = false;
throw e;
}
}
public async stop() {
try {
if (!this._running) {
return;
}
this._running = false;
await this.z.stop();
} catch (e) {
this._running = true;
throw e;
}
}
public async useYZ() {
if (!this._running) {
throw new Error('X is not running');
}
await this.y.doSomething();
await this.z.doSomething();
}
}
class Y {
protected _running: boolean = false;
public constructor() {
}
get running(): boolean {
return this._running;
}
public async start(): Promise<void> {
try {
if (this._running) {
return;
}
this._running = true;
} catch (e) {
this._running = false;
throw e;
}
}
public async stop() {
try {
if (!this._running) {
return;
}
this._running = false;
} catch (e) {
this._running = true;
throw e;
}
}
public async doSomething() {
if (!this._running) {
throw new Error('Y is not running');
}
console.log('Y did something');
}
}
class Z {
protected _running: boolean = false;
public constructor() {
}
get running(): boolean {
return this._running;
}
public async start(): Promise<void> {
try {
if (this._running) {
return;
}
this._running = true;
} catch (e) {
this._running = false;
throw e;
}
}
public async stop() {
try {
if (!this._running) {
return;
}
this._running = false;
} catch (e) {
this._running = true;
throw e;
}
}
public async doSomething() {
if (!this._running) {
throw new Error('Z is not running');
}
console.log('Z did something');
}
}
async function main() {
const y = new Y();
const x = new X({
y: y
});
// you can start y and x out of order
await y.start();
await x.start();
await x.useYZ();
}
/**
* Use this when you need to start and stop and explicitly destroy the object
* Once destroyed, you can create it again
* If you stop it, you can start it again
* Remember you can only destroy after stopping
*/
class X {
protected _running: boolean = false;
protected _destroyed: boolean = false;
public static async createX(): Promise<X> {
const x = new X;
await x.start();
return x;
}
protected constructor() {
}
get running() {
return this._running;
}
get destroyed() {
return this._destroyed;
}
public async start(): Promise<void> {
try {
if (this._running) {
return;
}
if (this._destroyed) {
throw new Error('X is destroyed');
}
this._running = true;
} catch (e) {
this._running = false;
throw e;
}
}
public async stop(): Promise<void> {
try {
if (!this._running) {
return;
}
if (this._destroyed) {
throw new Error('X is destroyed');
}
this._running = false;
} catch (e) {
this._running = true;
throw e;
}
}
public async destroy(): Promise<void> {
try {
if (this._destroyed) {
return;
}
if (this._running) {
throw new Error('X is running');
}
this._destroyed = true;
} catch (e) {
this._destroyed = false;
throw e;
}
}
public async doSomething(): Promise<void> {
if (!this._running) {
throw new Error('X is not running');
}
console.log('X did something');
}
}
async function main() {
const x = await X.createX(); // x is started on creation
await x.doSomething();
await x.start(); // this is a noop
await x.doSomething();
await x.stop(); // stops x
await x.start(); // restarts x
await x.doSomething();
await x.stop(); // stops x
await x.destroy(); // destroy x only after stopping
}
@CMCDragonkai
Copy link
Author

This is now generalised to https://github.com/MatrixAI/js-async-init

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment