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

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

其他回答

你完全可以通过从构造函数返回一个立即调用的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调用构造函数。

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

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> { ... }
...

这是可以做到的。 一个简单的代码:

class test
{
   constructor ()
   {
      return new Promise ( (resolve, reject) => { resolve(this); });
   }
   doHello() {console.log("hello");}
}
async function main()
{
   let t = await new test(); //invoking synchronously
   t.doHello(); //t is not a pormise
}
main();

或与上面相同,但添加了实际延迟,使用setTimeout

class test
{
   constructor ()
   {
      return new Promise ( (resolve, reject) =>
      {
         setTimeout (resolve, 5, this);
      });
   }
   doHello() {console.log("hello");}
}
async function main()
{  
   let t = new test(); //now t is a promise
   t.then((a)=>{ a.doHello();}); //a is the real reference to test instance
   console.log("testing"); //"testing" will be printed 5 seconds before "hello"
}
main();

这里是我在现实生活中的一段代码,使用异步图像加载:

class HeightMap extends GlVAObject
{
   #vertices = [];
   constructor (src, crossOrigin = "")
   {
      //super(theContextSetup);
      let image = new Image();
      image.src = src;
      image.crossOrigin = crossOrigin;
      return new Promise ( (resolve, reject) =>
         {
            image.addEventListener('load',  () =>
            {
               //reading pixel values from image into this.#vertices
               //and generate a heights map
               //...
               resolve(this);
            } );
         });
   }
///...
}
async function main()
{
   let vao = await new HeightMap ("./heightmaps/ArisonaCraterHeightMap.png");
///...
}
main();

这里有很多伟大的知识和一些超级()深思熟虑的回答。简而言之,下面概述的技术相当简单、非递归、异步兼容,并且遵循规则。更重要的是,我认为这里还没有正确地覆盖它-尽管如果错误请纠正我!

我们不调用方法,而是简单地将II(A)FE赋值给实例prop:

// it's async-lite!
class AsyncLiteComponent {
  constructor() {
    // our instance includes a 'ready' property: an IIAFE promise
    // that auto-runs our async needs and then resolves to the instance
    // ...
    // this is the primary difference to other answers, in that we defer
    // from a property, not a method, and the async functionality both
    // auto-runs and the promise/prop resolves to the instance
    this.ready = (async () => {
      // in this example we're auto-fetching something
      this.msg = await AsyncLiteComponent.msg;
      // we return our instance to allow nifty one-liners (see below)
      return this;
    })();
  }

  // we keep our async functionality in a static async getter
  // ... technically (with some minor tweaks), we could prefetch
  // or cache this response (but that isn't really our goal here)
  static get msg() {
    // yes I know - this example returns almost immediately (imagination people!)
    return fetch('data:,Hello%20World%21').then((e) => e.text());
  }
}

看起来很简单,怎么用呢?

// Ok, so you *could* instantiate it the normal, excessively boring way
const iwillnotwait = new AsyncLiteComponent();
// and defer your waiting for later
await iwillnotwait.ready
console.log(iwillnotwait.msg)

// OR OR OR you can get all async/awaity about it!
const onlywhenimready = await new AsyncLiteComponent().ready;
console.log(onlywhenimready.msg)

// ... if you're really antsy you could even "pre-wait" using the static method,
// but you'd probably want some caching / update logic in the class first
const prefetched = await AsyncLiteComponent.msg;

// ... and I haven't fully tested this but it should also be open for extension
class Extensior extends AsyncLiteComponent {
  constructor() {
    super();
    this.ready.then(() => console.log(this.msg))
  } 
}
const extendedwaittime = await new Extensior().ready;

在发帖之前,我在@slebetman的评论中对这种技术的可行性进行了简短的讨论。我并没有完全被这种直接的解雇所说服,所以我认为我应该进一步讨论/推翻它。请尽你最大的努力吧。

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

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

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