diff --git a/AsyncClass.js b/AsyncClass.js index e5efb64..01b5228 100644 --- a/AsyncClass.js +++ b/AsyncClass.js @@ -1,64 +1,80 @@ -const gen_prototypes = function*(instance) { - let proto = instance - do { - yield proto - } while (proto = Object.getPrototypeOf(proto)) +const genPrototypes = function * (instance) { + let proto = instance + do { + yield proto + } while (proto = Object.getPrototypeOf(proto)) } -const inheritance = Symbol("inheritance tracker") +const inheritance = Symbol('inheritanceTracker') -const async_constructor = Symbol("async constructor") -const async_super = Symbol("async super") +const constructor = Symbol('asyncConstructor') +const asyncSuper = Symbol('asyncSuper') -class AsyncClass { - constructor (...args) { - if (this[async_super] !== AsyncClass.prototype[async_super]) { - throw new ReferenceError("AsyncClass[[async super]] cannot be overriden") - } - return new Promise(async (resolve) => { - this[inheritance] = Object.getPrototypeOf(this) - const bound_constructor = this[inheritance].hasOwnProperty(async_constructor) - ? this[async_constructor].bind(this) - : AsyncClass.prototype[async_constructor].bind(this) - delete this[async_constructor] - const override = await bound_constructor(...args) - if (typeof this[inheritance] !== "undefined" && this[inheritance] !== null && typeof this[inheritance][async_constructor] !== "undefined") { - throw new ReferenceError("this[[async super]]() must be called by this[[async constructor]]()") - } - delete this[async_super] - delete this[inheritance] - resolve(typeof override !== "undefined" ? override : this) - }) +/** + * @type {import('./typedef.d.ts').AsyncClass} + */ +class Async { + constructor (...args) { + if (this[asyncSuper] !== Async.prototype[asyncSuper]) { + throw new ReferenceError('AsyncClass[[asyncSuper]] cannot be overriden') } + return new Promise(async (resolve) => { + this[inheritance] = Object.getPrototypeOf(this) + const boundConstructor = this[inheritance].hasOwnProperty(constructor) + ? this[constructor].bind(this) + : Async.prototype[constructor].bind(this) + delete this[constructor] + const override = await boundConstructor(...args) + if (typeof this[inheritance] !== 'undefined' && this[inheritance] !== null && typeof this[inheritance][constructor] !== 'undefined') { + throw new ReferenceError('this[[asyncSuper]]() must be called by this[[async constructor]]()') + } + delete this[asyncSuper] + delete this[inheritance] + resolve(typeof override !== 'undefined' ? override : this) + }) + } - async [async_constructor](...args) { - await this[async_super](...args) - } + async [constructor] (...args) { + await this[asyncSuper](...args) + } - async [async_super](...args) { - this[inheritance] = Object.getPrototypeOf(this[inheritance]) - if (typeof this[inheritance] !== "undefined" && this[inheritance] !== null && typeof this[inheritance][async_constructor] !== "undefined") { - if (this[inheritance].hasOwnProperty(async_constructor)) { - const override = await this[inheritance][async_constructor].bind(this)(...args) - if (typeof override !== "undefined") { - for (const proto of Array.from(gen_prototypes(this))) { - for (const key of Object.getOwnPropertyNames(proto)) { - delete this[key] - } - for (const key of Object.getOwnPropertySymbols(proto)) { - if (key !== async_constructor && key !== async_super && key !== inheritance) { - delete this[key] - } - } - } - Object.setPrototypeOf(this, Object.getPrototypeOf(override)) - Object.assign(this, override) - } - } else { - await AsyncClass.prototype[async_constructor].bind(this)(...args) + async [asyncSuper] (...args) { + this[inheritance] = Object.getPrototypeOf(this[inheritance]) + if (typeof this[inheritance] !== 'undefined' && this[inheritance] !== null && typeof this[inheritance][constructor] !== 'undefined') { + if (this[inheritance].hasOwnProperty(constructor)) { + const override = await this[inheritance][constructor].bind(this)(...args) + if (typeof override !== 'undefined') { + for (const proto of Array.from(genPrototypes(this))) { + for (const key of Object.getOwnPropertyNames(proto)) { + delete this[key] + } + for (const key of Object.getOwnPropertySymbols(proto)) { + if (key !== constructor && key !== asyncSuper && key !== inheritance) { + delete this[key] + } } + } + Object.setPrototypeOf(this, Object.getPrototypeOf(override)) + Object.assign(this, override) } + } else { + await Async.prototype[constructor].bind(this)(...args) + } + } + } +} + +/** + * @type {import('./typedef.d.ts').wrap} + */ +function wrap (_class) { + // @ts-ignore + return new Proxy(_class, { + construct: (Module, argumentsList) => { + const instance = new Module(...argumentsList) + return instance[constructor](...argumentsList) } + }) } -module.exports = {AsyncClass, async_constructor, async_super} \ No newline at end of file +module.exports = { Async, constructor, Super: asyncSuper, wrap } diff --git a/README.md b/README.md index 2e61c3c..db5b5a2 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ This module provides a class and the symbols necessary to create classes whose c The simplest async class looks like this: ```js -const {AsyncClass, async_constructor, async_super} = require("async-inheritance") +const { Async, constructor, Super } = require("async-inheritance") -class MyClass extends AsyncClass { - async [async_constructor]() { - await this[async_super]() +class MyClass extends Async { + async [constructor]() { + await this[Super]() // async code } } @@ -22,22 +22,35 @@ class MyClass extends AsyncClass { })() ``` -Classes that extend AsyncClass can then be extended further: +If you can't extend a function, simply wrap it: ```js -const {AsyncClass, async_constructor, async_super} = require("async-inheritance") +const { wrap } = require("async-inheritance") +const { EventEmitter } = require("events") + +const WrappedClass = wrap(class extends EventEmitter { + async [constructor]() { + await asyncCode() + } +}) +``` + +Classes that extend Async can then be extended further: + +```js +const { Async, constructor, Super } = require("async-inheritance") const timers = require("timers/promises") -class MyClass1 extends AsyncClass { - async [async_constructor](construction_delay) { - await this[async_super]() +class MyClass1 extends Async { + async [constructor](construction_delay) { + await this[Super]() await timers.setTimeout(construction_delay) } } class MyClass2 extends MyClass1 { - async [async_constructor]() { - await this[async_super](100) + async [constructor]() { + await this[Super](100) console.log("Finished constructor") } } @@ -47,15 +60,15 @@ class MyClass2 extends MyClass1 { })() ``` -If a child class overrides \[\[async_constructor]] it _must_ call \[\[async_super]], but the constructor can be omitted entirely with no issue: +If a child class overrides \[\[constructor]] it _must_ call \[\[Super]], but the constructor can be omitted entirely with no issue: ```js -const {AsyncClass, async_constructor, async_super} = require("async-inheritance") +const { Async, constructor, Super } = require("async-inheritance") const timers = require("timers/promises") -class MyClass1 extends AsyncClass { - async [async_constructor](construction_delay) { - await this[async_super]() +class MyClass1 extends Async { + async [constructor](construction_delay) { + await this[Super]() await timers.setTimeout(construction_delay) } do_something() { @@ -71,8 +84,8 @@ class MyClass2 extends MyClass1 { } class MyClass3 extends MyClass2 { - async [async_constructor]() { - await this[async_super](100) + async [constructor]() { + await this[Super](100) console.log("Finished constructor") } } @@ -85,11 +98,11 @@ class MyClass3 extends MyClass2 { Just like with normal constructors, the async constructor can return an object: ```js -const {AsyncClass, async_constructor, async_super} = require("async-inheritance") +const { Async, constructor, Super } = require("async-inheritance") -class MyClass extends AsyncClass { - async [async_constructor]() { - await this[async_super]() +class MyClass extends Async { + async [constructor]() { + await this[Super]() return {my_property: 1} } } @@ -103,7 +116,7 @@ class MyClass extends AsyncClass { ### Notes * Overriding the normal constructor will break the async constructor's functionality -* Acessing `this` before calling `await this[async_super]()` may cause unexpected behavior (it does NOT throw an error the way that normal JavaScript class inheritance does) +* Acessing `this` before calling `await this[Super]()` may cause unexpected behavior (it does NOT throw an error the way that normal JavaScript class inheritance does) ### Compatibility diff --git a/package.json b/package.json index b24784e..dbae841 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "Provide async constructor functionality to JavaScript classes", "main": "AsyncClass.js", + "types": "typedef.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/typedef.d.ts b/typedef.d.ts new file mode 100644 index 0000000..ac2f589 --- /dev/null +++ b/typedef.d.ts @@ -0,0 +1,9 @@ +export interface AsyncConstructable extends T { + new (...args: ConstructorParameters): Promise +} + +export interface AsyncClass { + new (...args: any[]): Promise +} + +export type wrap = (_class: T) => AsyncConstructable