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


当前回答

根据您的注释,您可能应该做其他带有资产加载的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>元素的单个实例,否则您不能使用唯一标识符,然后将其分配给一堆元素。

其他回答

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

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

我们不调用方法,而是简单地将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的评论中对这种技术的可行性进行了简短的讨论。我并没有完全被这种直接的解雇所说服,所以我认为我应该进一步讨论/推翻它。请尽你最大的努力吧。

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

这是行不通的。

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

你只能在可以使用承诺的地方使用async/await,因为它们本质上是承诺的语法糖。不能在构造函数中使用promise,因为构造函数必须返回要构造的对象,而不是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);
        }
    }

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


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

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

您应该将该函数添加到实例中。Promise将它识别为Promise的可支持对象。解决自动

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