异步/等待类构造函数

新手上路,请多包涵

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

原文由 Alexander Craggs 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 2k
2 个回答

永远 行不通。

async 关键字允许在标记为 async 的函数中使用 await ,但它也将该函数转换为 promise 生成器。因此,标有 async 的函数将返回一个 Promise。另一方面,构造函数返回它正在构造的对象。因此,我们遇到了一种情况,您希望同时返回一个对象和一个承诺:一种不可能的情况。

您只能在可以使用 Promise 的地方使用 async/await,因为它们本质上是 Promise 的语法糖。您不能在构造函数中使用 Promise,因为构造函数必须返回要构造的对象,而不是 Promise。

有两种设计模式可以克服这个问题,它们都是在 Promise 出现之前发明的。

  1. 使用 init() 函数。这有点像 jQuery 的 .ready() 。您创建的对象只能在它自己的 initready 函数中使用:

用法:

 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)();
 }
 }

  1. 使用生成器。我没有看到这在 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);
 }
 }

注意:虽然在上面的例子中我们使用了异步构建器的 Promise,但严格来说它们并不是必需的。您可以轻松地编写一个接受回调的构建器。


注意在静态函数中调用函数。

这与异步构造函数无关,而是与关键字 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 () {}
 }

原文由 slebetman 发布,翻译遵循 CC BY-SA 3.0 许可协议

您绝对 可以 通过从构造函数返回一个 立即调用的异步函数表达式 来做到这一点。 IIAFE 是一个非常常见的模式的奇特名称,在 顶级 await 可用之前,需要在异步函数之外使用 await

 (async () => {
  await someFunction();
})();

我们将使用此模式立即在构造函数中执行异步函数,并将其结果返回为 this

 // 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
  }
}

缺点

  1. 使用异步构造函数扩展类会有限制。如果您需要在派生类的构造函数中调用 super ,则必须在没有 await 的情况下调用它。如果您需要使用 await 调用超级构造函数,您将遇到 TypeScript 错误 2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
  2. 有人认为让 构造函数返回 Promise 是一种“坏习惯”。

在使用此解决方案之前,确定您是否需要扩展该类,并记录必须使用 await 调用构造函数。

原文由 Downgoat 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏