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


当前回答

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

我们不调用方法,而是简单地将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 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方法?

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

不像其他人说的,你可以让它工作。

JavaScript类可以从它们的构造函数返回任何东西,甚至是另一个类的实例。因此,您可以从解析到其实际实例的类的构造函数返回一个Promise。

下面是一个例子:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

            return this; // Return the newly-created instance
        })();
    }
}

然后,你将这样创建Foo的实例:

const foo = await new Foo();

@slebetmen的公认答案很好地解释了为什么这行不通。除了答案中给出的两种模式之外,另一种选择是仅通过自定义异步getter访问您的异步属性。然后构造函数()可以触发属性的异步创建,但是getter在使用或返回属性之前检查属性是否可用。

当你想在启动后初始化一个全局对象,并且你想在一个模块中做这件事时,这种方法特别有用。而不是在index.js中初始化并将实例传递到需要它的地方,只需在需要全局对象的地方调用模块。

使用

const instance = new MyClass();
const prop = await instance.getMyProperty();

实现

class MyClass {
  constructor() {
    this.myProperty = null;
    this.myPropertyPromise = this.downloadAsyncStuff();
  }
  async downloadAsyncStuff() {
    // await yourAsyncCall();
    this.myProperty = 'async property'; // this would instead by your async call
    return this.myProperty;
  }
  getMyProperty() {
    if (this.myProperty) {
      return this.myProperty;
    } else {
      return this.myPropertyPromise;
    }
  }
}