目前,我正在尝试在类构造函数中使用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()?
你完全可以通过从构造函数返回一个立即调用的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调用构造函数。
根据您的注释,您可能应该做其他带有资产加载的HTMLElement所做的事情:使构造函数启动一个侧加载操作,根据结果生成一个加载或错误事件。
是的,这意味着使用承诺,但这也意味着“以与其他HTML元素相同的方式做事”,所以你是一个很好的公司。例如:
var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";
这将启动源资产的异步加载,当它成功时,以onload结束,当它出错时,以onerror结束。所以,让你自己的类也这样做:
class EMailElement extends HTMLElement {
connectedCallback() {
this.uid = this.getAttribute('data-uid');
}
setAttribute(name, value) {
super.setAttribute(name, value);
if (name === 'data-uid') {
this.uid = value;
}
}
set uid(input) {
if (!input) return;
const uid = parseInt(input);
// don't fight the river, go with the flow, use a promise:
new Promise((resolve, reject) => {
yourDataBase.getByUID(uid, (err, result) => {
if (err) return reject(err);
resolve(result);
});
})
.then(result => {
this.renderLoaded(result.message);
})
.catch(error => {
this.renderError(error);
});
}
};
customElements.define('e-mail', EmailElement);
然后你让renderLoaded/renderError函数处理事件调用和shadow dom:
renderLoaded(message) {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">A random email message has appeared. ${message}</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onload(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('load'));
}
renderFailed() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<div class="email">No email messages.</div>
`;
// is there an ancient event listener?
if (this.onload) {
this.onerror(...);
}
// there might be modern event listeners. dispatch an event.
this.dispatchEvent(new Event('error'));
}
还要注意,我将id更改为一个类,因为除非您编写一些奇怪的代码,只允许页面上的<e-mail>元素的单个实例,否则您不能使用唯一标识符,然后将其分配给一堆元素。