目前,我正在尝试在类构造函数中使用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()?


当前回答

最接近异步构造函数的方法是等待它完成执行,如果它还没有在它的所有方法中执行:

class SomeClass {
    constructor() {
        this.asyncConstructor = (async () => {
            // Perform asynchronous operations here
        })()
    }

    async someMethod() {
        await this.asyncConstructor
        // Perform normal logic here
    }
}

其他回答

你可以使用Proxy的构造句柄来做到这一点,代码如下:

const SomeClass = new Proxy(class A {
  constructor(user) {
    this.user = user;
  }
}, {
  async construct(target, args, newTarget) {
    const [name] = args;
    // you can use await in here
    const user = await fetch(name);
    // invoke new A here
    return new target(user);
  }
});

const a = await new SomeClass('cage');
console.log(a.user); // user info

权宜之计

你可以创建一个async init(){…返回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}

However say you want to use this pattern on many objects... you could abstract this as a decorator or something you (verbosely, ugh) call after defining like postProcess_makeAsyncInit(AsyncOnlyObject), but here I'm going to use extends because it sort of fits into subclass semantics (subclasses are parent class + extra, in that they should obey the design contract of the parent class, and may do additional things; an async subclass would be strange if the parent wasn't also async, because it could not be initialized the same way):


抽象解决方案(扩展/子类版本)

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}

(不要在生产中使用:我没有考虑过复杂的场景,比如这是否是为关键字参数编写包装器的正确方式。)

如果可以避免扩展,则可以一起避免类,并使用函数组合作为构造函数。你可以使用作用域中的变量代替类成员:

async function buildA(...) {
  const data = await fetch(...);
  return {
    getData: function() {
      return data;
    }
  }
}

简单的使用它

const a = await buildA(...);

如果你使用typescript或flow,你甚至可以强制构造函数的接口

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...

因为async函数是承诺,你可以在你的类上创建一个静态函数,它执行一个返回类实例的async函数:

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()。

您可以立即调用一个返回消息的匿名异步函数,并将其设置为message变量。如果您不熟悉这种模式,您可能想看看立即调用的函数表达式(IEFES)。这将非常有效。

var message = (async function() { return await grabUID(uid) })()