目前,我正在尝试在类构造函数中使用async/await。这样我就可以为我正在进行的Electron项目获得一个自定义的电子邮件标签。
customElements.define('e-mail', class extends HTMLElement {
async constructor() {
super()
let uid = this.getAttribute('data-uid')
let message = await grabUID(uid)
const shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.innerHTML = `
<div id="email">A random email message has appeared. ${message}</div>
`
}
})
然而,目前项目不工作,有以下错误:
Class constructor may not be an async method
是否有一种方法来规避这一点,以便我可以使用异步/等待在这?而不是要求回调或.then()?
@slebetmen的公认答案很好地解释了为什么这行不通。除了答案中给出的两种模式之外,另一种选择是仅通过自定义异步getter访问您的异步属性。然后构造函数()可以触发属性的异步创建,但是getter在使用或返回属性之前检查属性是否可用。
当你想在启动后初始化一个全局对象,并且你想在一个模块中做这件事时,这种方法特别有用。而不是在index.js中初始化并将实例传递到需要它的地方,只需在需要全局对象的地方调用模块。
使用
const instance = new MyClass();
const prop = await instance.getMyProperty();
实现
class MyClass {
constructor() {
this.myProperty = null;
this.myPropertyPromise = this.downloadAsyncStuff();
}
async downloadAsyncStuff() {
// await yourAsyncCall();
this.myProperty = 'async property'; // this would instead by your async call
return this.myProperty;
}
getMyProperty() {
if (this.myProperty) {
return this.myProperty;
} else {
return this.myPropertyPromise;
}
}
}
你完全可以通过从构造函数返回一个立即调用的Async函数表达式来做到这一点。IIAFE是一个非常常见的模式,在顶级await可用之前,需要在异步函数之外使用await:
(async () => {
await someFunction();
})();
我们将使用此模式立即在构造函数中执行async函数,并返回其结果如下:
// Sample async function to be used in the async constructor
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
class AsyncConstructor {
constructor(value) {
return (async () => {
// Call async functions here
await sleep(500);
this.value = value;
// Constructors return `this` implicitly, but this is an IIFE, so
// return `this` explicitly (else we'd return an empty object).
return this;
})();
}
}
(async () => {
console.log('Constructing...');
const obj = await new AsyncConstructor(123);
console.log('Done:', obj);
})();
要实例化类,使用:
const instance = await new AsyncConstructor(...);
对于TypeScript,你需要断言构造函数的类型是类类型,而不是返回类类型的promise:
class AsyncConstructor {
constructor(value) {
return (async (): Promise<AsyncConstructor> => {
// ...
return this;
})() as unknown as AsyncConstructor; // <-- type assertion
}
}
缺点
使用异步构造函数扩展类会有限制。如果需要在派生类的构造函数中调用super,则必须在没有await的情况下调用它。如果你需要使用await调用超级构造函数,你会遇到TypeScript错误2337:超级调用不允许在构造函数外部或构造函数内部的嵌套函数中调用。
有人认为让构造函数返回Promise是一种“坏习惯”。
在使用此解决方案之前,确定是否需要扩展类,并记录必须使用await调用构造函数。
我根据@Downgoat的回答制作了这个测试用例。
它运行在NodeJS上。
这是Downgoat的代码,其中异步部分由setTimeout()调用提供。
'use strict';
const util = require( 'util' );
class AsyncConstructor{
constructor( lapse ){
this.qqq = 'QQQ';
this.lapse = lapse;
return ( async ( lapse ) => {
await this.delay( lapse );
return this;
})( lapse );
}
async delay(ms) {
return await new Promise(resolve => setTimeout(resolve, ms));
}
}
let run = async ( millis ) => {
// Instatiate with await, inside an async function
let asyncConstructed = await new AsyncConstructor( millis );
console.log( 'AsyncConstructor: ' + util.inspect( asyncConstructed ));
};
run( 777 );
我的用例是web应用程序服务器端的dao。
当我看到dao时,它们每个都关联到一个记录格式,在我的例子中是一个MongoDB集合,例如一个厨师。
cooksDAO实例保存厨师的数据。
在我不安的思绪中,我将能够实例化一个cook的DAO,并提供cookId作为参数,实例化将创建对象并使用cook的数据填充它。
因此需要在构造函数中运行异步的东西。
我想写:
let cook = new cooksDAO( '12345' );
来拥有可用的属性,如cook.getDisplayName()。
有了这个解决方案,我必须做:
let cook = await new cooksDAO( '12345' );
这和理想情况非常相似。
此外,我需要在一个异步函数中做到这一点。
我的b计划是把数据加载放在构造函数之外,基于@slebetman的建议使用init函数,并像这样做:
let cook = new cooksDAO( '12345' );
async cook.getData();
这并不违反规则。