Small utility to add async constructors to JavaScript/TypeScript
npm i @baked-dev/async-class --save-dev
yarn add @baked-dev/async-class --dev
pnpm i @baked-dev/async-class --save-dev
/** * add the parameter types of the construct method as a tuple as the generic for AsyncClass. * (these types can not be Inferred from usage in the construct function at the moment) */classTestextendsAsyncClass<[string]>{publictest="asd";/** * the construct method replaces the constructor and should be async. * without a custom constructor the parameters of the constructor * will match this construct method. * has to be a member method as it needs to be available before super() * is called */protectedasyncconstruct(test: string){console.log(test);awaitnewPromise((res)=>setTimeout(res,1000));}publiclog=async()=>{awaitthis;// wait for contruction to finishconsole.log(this.test);};}constmain=async()=>{constawaitable=newTest("hallo");// get the "async constructor"consttest=awaitawaitable;// await the class "construction"consttest2=awaitawaitable;// can be awaited multiple timesawaitable.then(test3=>{test3.log();// -> "asd"});// can be chainedconsttest4=awaitnewTest("hallo2");// await directlyconsole.log(test===test2);// -> trueconsole.log(testinstanceofTest);// -> truetest.test="123";test.log();// -> "123"}main();the same but without types
AsyncClass class implements the Promise interface.
exportabstractclassAsyncClass<Cextendsany[]=[]>implementsPromise<any>In the constructor the construct method supplied by the extending class is called and attached to this.__construct:
constructor(...args: C){this.__construct=this.construct(...args);}.catch and .finally just proxy to the this.__construct:
publiccatch<TResult=never>(onrejected?:
|((reason: any)=>TResult|PromiseLike<TResult>)|null|undefined): Promise<any>{returnthis.__construct.catch(onrejected);}publicfinally(onfinally?: (()=>void)|null|undefined): Promise<any>{returnthis.__construct.finally(onfinally);}.then is intercepted and resolves with a proxied instance:
publicthen<TResult1=any,TResult2=never>(onfulfilled: (value: ResolvedInstance<this>)=>TResult1|PromiseLike<TResult1>,onrejected?:
|((reason: any)=>TResult2|PromiseLike<TResult2>)|null|undefined): Promise<TResult1|TResult2>{returnthis.__construct.then(()=>{returnonfulfilled(this.__proxy);},onrejected);}The proxied instance of this removes the Promise and internal Interfaces, else this would result in an infinite loop as Javascript eagerly awaits promises:
privatereadonly __proxy: ResolvedInstance<this>=newProxy(this,{get: (target,prop)=>{if(typeofprop==="string"&&AsyncClass.hiddenProps.includes(prop))returnundefined;elsereturnthis[propaskeyoftypeoftarget];},has: (target,prop)=>{if(typeofprop==="string"&&AsyncClass.hiddenProps.includes(prop))returnfalse;returntarget.hasOwnProperty(prop);},ownKeys: (target)=>{returnObject.keys(target).filter((key)=>!AsyncClass.hiddenProps.includes(key));},});