目前,我正在尝试在类构造函数中使用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
有没有办法绕过这个,以便我可以在其中使用async / await?而不是要求回调或.then()?
答案 0 :(得分:128)
这可以从不工作。
async
关键字允许await
在标记为async
的函数中使用,但它也会将该函数转换为promise生成器。因此,标有async
的函数将返回一个承诺。另一方面,构造函数返回它正在构造的对象。因此,我们有一种情况,你想要返回一个对象和一个承诺:一个不可能的情况。
你只能使用async / await来使用promises,因为它们本质上是promises的语法糖。你不能在构造函数中使用promises,因为构造函数必须返回要构造的对象,而不是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
});
// with async/await:
async function foo () {
var myObj = await myClass.build();
}
实现:
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);
});
}
}
使用async / await实现:
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);
}
}
注意:虽然在上面的示例中我们使用了异步构建器的promises,但严格来说它们并不是必需的。您可以轻松编写接受回调的构建器。
这与异步构造函数没有任何关系,但与关键字this
实际意味着什么(对于那些来自自动解析方法名称的语言的人来说,这可能有点令人惊讶,也就是语言,不需要this
关键字)。
this
关键字指的是实例化对象。不是班级。因此,您通常不能在静态函数中使用this
,因为静态函数没有绑定到任何对象,而是直接绑定到类。
也就是说,在以下代码中:
class A {
static foo () {}
}
你做不到:
var a = new A();
a.foo() // NOPE!!
而你需要将其称为:
A.foo();
因此,以下代码会导致错误:
class A {
static foo () {
this.bar(); // you are calling this as static
// so bar is undefinned
}
bar () {}
}
要修复它,您可以使bar
成为常规函数或静态方法:
function bar1 () {}
class A {
static foo () {
bar1(); // this is OK
A.bar2(); // this is OK
}
static bar2 () {}
}
答案 1 :(得分:42)
你可以绝对这样做。基本上是:
class AsyncConstructor {
constructor() {
return (async () => {
// All async code here
this.value = await asyncFunction();
return this; // when done
})();
}
}
创建类使用:
let instance = await new AsyncConstructor();
注意:如果您需要使用super,则无法在异步回调中调用它。你必须在它之外调用它,所以这个解决方案不 100%完美,但在我看来它非常惯用,我在我的代码中一直使用它。
答案 2 :(得分:5)
不同于其他人所说的,您可以使其正常工作。
JavaScript class
可以从其constructor
返回任何内容,甚至可以返回另一个类的实例。因此,您可能会从类的构造函数返回Promise
来解析其实际实例。
下面是一个示例:
export class Foo {
constructor() {
return (async () => {
// await anything you want
return this; // Return the newly-created instance
})();
}
}
然后,您将通过以下方式创建Foo
的实例:
const foo = await new Foo();
答案 3 :(得分:4)
根据你的评论,你应该做每个其他带有资产加载的HTMLElement做的事情:让构造函数启动一个sideloading动作,根据结果生成一个load或error事件。
是的,这意味着使用promises,但它也意味着“以与其他所有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>
元素的单个实例,否则您可以'使用唯一标识符,然后将其分配给一堆元素。
答案 4 :(得分:3)
因为异步函数是promise,所以你可以在类上创建一个静态函数,它执行一个异步函数,它返回类的实例:
class Yql {
constructor () {
// Set up your class
}
static init () {
return (async function () {
let yql = new Yql()
// Do async stuff
await yql.build()
// Return instance
return yql
}())
}
async build () {
// Do stuff with await if needed
}
}
async function yql () {
// Do this instead of "new Yql()"
let yql = await Yql.init()
// Do stuff with yql instance
}
yql()
从异步函数调用let yql = await Yql.init()
。
答案 5 :(得分:2)
我根据@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集合,例如Cook。
cooksDAO实例保存厨师的数据。
在我烦躁不安的头脑中,我将能够实例化一个厨师的DAO,将cookId作为参数,实例化将创建对象并用厨师的数据填充它。
因此需要在构造函数中运行异步内容。
我想写:
let cook = new cooksDAO( '12345' );
具有cook.getDisplayName()
之类的可用属性。
使用此解决方案,我必须要做:
let cook = await new cooksDAO( '12345' );
与理想非常相似。
另外,我需要在async
函数中执行此操作。
我的B计划是根据@slebetman建议使用init函数将数据装入构造函数,然后执行以下操作:
let cook = new cooksDAO( '12345' );
async cook.getData();
这不会违反规则。
答案 6 :(得分:1)
在构造中使用异步方法?
constructor(props) {
super(props);
(async () => await this.qwe(() => console.log(props), () => console.log(props)))();
}
async qwe(q, w) {
return new Promise((rs, rj) => {
rs(q());
rj(w());
});
}
答案 7 :(得分:1)
如果可以避免 extend
,则可以避免所有类,并且可以将函数组合用作构造函数。您可以在范围中使用变量而不是类成员:
async function buildA(...) {
const data = await fetch(...);
return {
getData: function() {
return data;
}
}
}
并简单地将其用作
const a = await buildA(...);
如果您使用打字稿或流,甚至可以强制使用构造函数的界面
Interface A {
getData: object;
}
async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
答案 8 :(得分:1)
您可以创建一个async init() {... return this;}
方法,然后在通常只说new MyClass().init()
的情况下执行new MyClass()
。
这并不干净,因为它依赖于使用您的代码的每个人以及您自己,始终像这样实例化该对象。但是,如果仅在代码中的一个或两个特定位置使用此对象,则可能会很好。
由于ES没有类型系统,因此会出现一个严重的问题,因此,如果您忘记调用它,则只会返回undefined
,因为构造函数什么也不会返回。哎呀。更好的做法是:
最好的办法是:
class AsyncOnlyObject {
constructor() {
}
async init() {
this.someField = await this.calculateStuff();
}
async calculateStuff() {
return 5;
}
}
async function newAsync_AsyncOnlyObject() {
return await new AsyncOnlyObject().init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}
但是,那么您可能会意外地创建新的AsyncOnlyObject,您可能应该只创建直接使用Object.create(AsyncOnlyObject.prototype)
的工厂函数:
async function newAsync_AsyncOnlyObject() {
return await Object.create(AsyncOnlyObject.prototype).init();
}
newAsync_AsyncOnlyObject().then(console.log);
// output: AsyncOnlyObject {someField: 5}
但是,您说您想在许多对象上使用此模式...您可以将其抽象为装饰器或定义为postProcess_makeAsyncInit(AsyncOnlyObject)
后调用的对象(俗称ugh),但是在这里我将使用extends
,因为它适合子类的语义(子类是父类+额外的子类,因为它们应遵守父类的设计约定,并可能做其他事情;如果父类不是子类,那么异步子类会很奇怪也不是异步的,因为它不能以相同的方式初始化):
class AsyncObject {
constructor() {
throw new Error('classes descended from AsyncObject must be initialized as (await) TheClassName.anew(), rather than new TheClassName()');
}
static async anew(...args) {
var R = Object.create(this.prototype);
R.init(...args);
return R;
}
}
class MyObject extends AsyncObject {
async init(x, y=5) {
this.x = x;
this.y = y;
// bonus: we need not return 'this'
}
}
MyObject.anew('x').then(console.log);
// output: MyObject {x: "x", y: 5}
(请勿在生产环境中使用:我还没有考虑过复杂的场景,例如这是否是为关键字参数编写包装的正确方法。)
答案 9 :(得分:0)
构建器模式的变化,使用call():
function asyncMethod(arg) {
function innerPromise() { return new Promise((...)=> {...}) }
innerPromise().then(result => {
this.setStuff(result);
}
}
const getInstance = async (arg) => {
let instance = new Instance();
await asyncMethod.call(instance, arg);
return instance;
}
答案 10 :(得分:0)
您可以立即调用返回消息的匿名异步函数并将其设置为message变量。如果您不熟悉这种模式,则可能需要看一下立即调用的函数表达式(IEFES)。这将像一种魅力。
var message = (async function() { return await grabUID(uid) })()
答案 11 :(得分:0)
这里有一些简单的事情。它适用于 TypeScript。
class Foo {
private promiseReady;
constructor() {
this.promiseReady = this.init();
}
private async init() {
await someAsyncStuff();
}
public ready() {
return this.promiseReady;
}
}
与承诺一起使用:
let foo = new Foo();
foo.ready().then(() =>{
// It's ready
});
与异步/等待一起使用:
let foo= new Foo();
await foo.ready();
答案 12 :(得分:-1)
其他答案缺少明显的答案。只需从构造函数中调用异步函数:
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>
`
}
答案 13 :(得分:-1)
@slebetmen接受的答案很好地解释了为什么它不起作用。除了该答案中介绍的两种模式之外,另一种选择是仅通过自定义异步获取器访问异步属性。然后,constructor()可以触发属性的异步创建,但是getter然后在使用或返回属性之前检查该属性是否可用。
当您要在启动时初始化全局对象,并且想在模块内部进行初始化时,此方法特别有用。无需初始化index.js
并将实例传递到需要它的位置,而只需require
将模块放在需要全局对象的任何地方。
用法
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;
}
}
}
答案 14 :(得分:-1)
与异步构造函数最接近的是,如果尚未在其所有方法中都使用异步构造函数,则等待它完成执行:
class SomeClass {
constructor() {
this.asyncConstructor = (async () => {
// Perform asynchronous operations here
})()
}
async someMethod() {
await this.asyncConstructor
// Perform normal logic here
}
}
答案 15 :(得分:-2)
您应该将then
函数添加到实例。 Promise
会将其识别为具有Promise.resolve
自动
const asyncSymbol = Symbol();
class MyClass {
constructor() {
this.asyncData = null
}
then(resolve, reject) {
return (this[asyncSymbol] = this[asyncSymbol] || new Promise((innerResolve, innerReject) => {
this.asyncData = { a: 1 }
setTimeout(() => innerResolve(this.asyncData), 3000)
})).then(resolve, reject)
}
}
async function wait() {
const asyncData = await new MyClass();
alert('run 3s later')
alert(asyncData.a)
}