异步/等待类构造函数
此刻,我试图在类构造函数中使用async/await
。这样我就可以为我正在进行的Electron项目获得自定义e-mail
标签。异步/等待类构造函数
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
是否有办法规避,这样我可以在此使用异步/ AWAIT?而不是要求回调或.then()?
这可以从来没有工作。
async
关键字允许await
用于标记为async
的功能,但它也将该功能转换为承诺生成器。所以标有async
的函数将返回一个承诺。另一方面,构造函数返回它正在构造的对象。因此,我们有一种情况,你想要返回一个对象和一个承诺:一个不可能的情况。
您只能使用async/await在哪里使用promise,因为它们本质上是承诺的语法糖。您不能在构造函数中使用promise,因为构造函数必须返回要构造的对象,而不是承诺。
有两种设计模式可以解决这个问题,这两种设计模式都是在承诺前发明的。
-
使用
init()
功能。这有点像jQuery的.ready()
。您创建的对象只能是自己的init
或ready
函数内部使用:用法:
var myObj = new myClass(); myObj.init(function() { // inside here you can use myObj });
实现:
class myClass { constructor() { } init (callback) { // do something async and call the callback: callback.bind(this)(); } }
-
使用建设者。我没有看到这在JavaScript中使用过很多,但是当一个对象需要异步构建时,这是Java中更常见的解决方法之一。当然,构建器模式在构建需要大量复杂参数的对象时使用。这正是异步构建器的用例。不同的是,一个异步建设者不返回一个对象,但该对象的承诺:
用法:
myClass.build().then(function(myObj) { // myObj is returned by the promise, // not by the constructor // or builder });
实现:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static build() { return doSomeAsyncStuff() .then(function(async_result){ return new myClass(async_result); }); } }
实现与异步/等待:
class myClass { constructor (async_param) { if (typeof async_param === 'undefined') { throw new Error('Cannot be called directly'); } } static async build() { var async_result = await doSomeAsyncStuff(); return new myClass(async_result); } }
Note: although in the examples above we use promises for the async builder they are not strictly speaking necessary. You can just as easily write a builder that accept a callback.
请注意,根据这些注释,这个想法是这是一个html元素,它通常没有手动的'init()',但具有与'src'或'href'(在本例中为'data-uid')等特定属性相关的功能,这意味着使用一个setter绑定并在每次绑定新值时启动init(也可能在构建过程中,但是当然不会等待所产生的代码路径) –
根据你的意见,你可能应该做任何其他HTMLElement的资产加载:做构造函数启动一个sideloading动作,根据结果产生一个负载或错误事件。
是的,这意味着使用承诺,但它也意味着“以与其他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 {
constructor() {
super();
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
let getEmail = new Promise((resolve, reject) => {
yourDataBase.getByUID(uid, (err, result) => {
if (err) return reject(err);
resolve(result);
});
});
// kick off the promise, which will be async all on its own
getEmail()
.then(result => {
this.renderLoaded(result.message);
})
.catch(error => {
this.renderError(error);
});
}
};
customElements.define('e-mail', EmailElement);
然后你让renderLoaded/renderError功能处理事件调用和阴影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
到class
,因为除非您编写一些奇怪的代码,以便在页面上只允许单个实例的<e-mail>
元素,否则不能使用唯一标识符,然后将其分配给一组元素。
您还可以创建在构造函数自动执行的异步匿名函数:
class MyClass {
constructor() {
(async() => {
let result = await foo();
this.someProperty = result;
console.log(this.someProperty);// outputs: bar
})();
}
}
function foo() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('bar'), 2000);
});
}
new MyClass();
这不符合您的期望。而不是'new MyClass()',尝试'const obj = new MyClass();的console.log(OBJ。someProperty)'。你会得到'undefined',因为'someProperty'只会在*对象被创建后被赋予“bar”2000 ms *。期望的是,一旦我创建了一个对象,它已经设置了'someProperty'。这段代码说明了为什么构造函数*不应该是异步的。 –
我同意。虽然我确实认为对于某些使用情况,这种解决方案非常好,但我不会推荐它。当你需要在你的构造函数中做一些异步操作时,无论如何你的应用程序的设计可能会更好。 – afterburn
您应该添加then
函数实例。 Promise
将其识别为一个thenable对象与Promise.resolve
自动
const asyncSymbol = Symbol();
class MyClass {
constructor() {
// nothing to do with constructor
}
then(resolve, reject) {
return (this[asyncSymbol] = this[asyncSymbol] || new Promise((innerResolve, innerReject) => {
setTimeout(() => innerResolve(this), 3000)
})).then(resolve, reject)
}
}
async function wait() {
const myInstance = await new MyClass();
alert('run 3s later')
}
'innerResolve(this)'将不起作用,因为'this'仍然是可用的。这导致了一个永无止境的递归解决方案。 – Bergi
对不起,我的错误没有覆盖'then'函数的返回来避免递归 –
其他的答案缺少明显。只需拨打一个异步函数从你的构造:
constructor() {
setContentAsync();
}
async setContentAsync() {
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>
`
}
像[另一个“明显的”答案在这里](https://*.com/a/47611907/1269037),这一个不会做程序员通常期望构造函数的内容,即在创建对象时设置内容。 –
@DanDascalescu它是异步设置的,这正是提问者所要求的。你的观点是,创建对象时不会同步设置内容,问题不需要该内容。这就是为什么这个问题是关于在构造函数中使用await/async的原因。我已经演示了如何通过调用异步函数从构造函数中调用尽可能多的await/async。我已经完美地回答了这个问题。 – Navigateur
构造函数的目的就是为了你分配一个对象,然后立即返回。你可以更具体地了解*为什么*你认为你的构造函数应该是异步的?因为我们几乎可以保证在这里处理[XY问题](https://meta.stackexchange.com/a/66378)。 –
@ Mike'Pomax'Kamermans这很可能。基本上,我需要查询数据库才能获取加载此元素所需的元数据。查询数据库是一个异步操作,因此我需要一些方法来在构造元素之前等待它完成。我宁愿不使用回调,因为我在整个项目的其余部分都使用了等待/异步,并希望保持连续性。 –
@ Mike'Pomax'Kamermans这是一个电子邮件客户端,其中每个HTML元素看起来都类似于'',并且使用'customElements .define()'方法。 –