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


当前回答

你可以使用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函数是承诺,你可以在你的类上创建一个静态函数,它执行一个返回类实例的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()。

根据您的注释,您可能应该做其他带有资产加载的HTMLElement所做的事情:使构造函数启动一个侧加载操作,根据结果生成一个加载或错误事件。

是的,这意味着使用承诺,但这也意味着“以与其他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 {
  connectedCallback() {
    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, use a promise:
    new Promise((resolve, reject) => {
      yourDataBase.getByUID(uid, (err, result) => {
        if (err) return reject(err);
        resolve(result);
      });
    })
    .then(result => {
      this.renderLoaded(result.message);
    })
    .catch(error => {
      this.renderError(error);
    });
  }
};

customElements.define('e-mail', EmailElement);

然后你让renderLoaded/renderError函数处理事件调用和shadow 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更改为一个类,因为除非您编写一些奇怪的代码,只允许页面上的<e-mail>元素的单个实例,否则您不能使用唯一标识符,然后将其分配给一堆元素。

我发现自己遇到了这样的情况,最终使用了IIFE

// using TypeScript

class SomeClass {
    constructor() {
        // do something here
    }

    doSomethingAsync(): SomeClass {
        (async () => await asyncTask())();
        return this;
    }
}

const someClass = new SomeClass().doSomethingAsync();

如果你有其他依赖于异步任务的任务,你可以在IIFE完成执行后运行它们。

在构造函数中使用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());
    });
}

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

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