简体   繁体   English

异步/等待 Class 构造函数

[英]Async/Await Class Constructor

At the moment, I'm attempting to use async/await within a class constructor function. This is so that I can get a custom e-mail tag for an Electron project I'm working on.目前,我正在尝试在 class 构造函数 function 中使用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>
    `
  }
})

At the moment however, the project does not work, with the following error:但是,目前该项目不起作用,并出现以下错误:

Class constructor may not be an async method

Is there a way to circumvent this so that I can use async/await within this?有没有办法绕过这个,这样我就可以在其中使用异步/等待? Instead of requiring callbacks or.then()?而不是要求回调 or.then()?

This can never work.永远行不通。

The async keyword allows await to be used in a function marked as async but it also converts that function into a promise generator. async关键字允许在标记为async的函数中使用await ,但它也将该函数转换为 promise 生成器。 So a function marked with async will return a promise.因此,标有async的函数将返回一个 Promise。 A constructor on the other hand returns the object it is constructing.另一方面,构造函数返回它正在构造的对象。 Thus we have a situation where you want to both return an object and a promise: an impossible situation.因此,我们遇到了一种情况,您希望同时返回一个对象和一个承诺:一种不可能的情况。

You can only use async/await where you can use promises because they are essentially syntax sugar for promises.您只能在可以使用 Promise 的地方使用 async/await,因为它们本质上是 Promise 的语法糖。 You can't use promises in a constructor because a constructor must return the object to be constructed, not a promise.您不能在构造函数中使用 Promise,因为构造函数必须返回要构造的对象,而不是 Promise。

There are two design patterns to overcome this, both invented before promises were around.有两种设计模式可以克服这个问题,它们都是在 Promise 出现之前发明的。

  1. Use of an init() function.使用init()函数。 This works a bit like jQuery's .ready() .这有点像 jQuery 的.ready() The object you create can only be used inside it's own init or ready function:您创建的对象只能在它自己的initready函数中使用:

    Usage:用法:

     var myObj = new myClass(); myObj.init(function() { // inside here you can use myObj });

    Implementation:执行:

     class myClass { constructor () { } init (callback) { // do something async and call the callback: callback.bind(this)(); } }
  2. Use a builder.使用生成器。 I've not seen this used much in javascript but this is one of the more common work-arounds in Java when an object needs to be constructed asynchronously.我没有看到这在 javascript 中被大量使用,但是当需要异步构造对象时,这是 Java 中更常见的解决方法之一。 Of course, the builder pattern is used when constructing an object that requires a lot of complicated parameters.当然,构建器模式在构造需要大量复杂参数的对象时使用。 Which is exactly the use-case for asynchronous builders.这正是异步构建器的用例。 The difference is that an async builder does not return an object but a promise of that object:不同之处在于异步构建器不返回对象,而是返回该对象的承诺:

    Usage:用法:

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

    Implementation:执行:

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

    Implementation with async/await:使用 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); } }

Note: although in the examples above we use promises for the async builder they are not strictly speaking necessary.注意:虽然在上面的例子中我们使用了异步构建器的 Promise,但严格来说它们并不是必需的。 You can just as easily write a builder that accept a callback.您可以轻松地编写一个接受回调的构建器。


Note on calling functions inside static functions.注意在静态函数中调用函数。

This has nothing whatsoever to do with async constructors but with what the keyword this actually mean (which may be a bit surprising to people coming from languages that do auto-resolution of method names, that is, languages that don't need the this keyword).这与异步构造函数无关,而是与关键字this的实际含义有关(对于来自对方法名称进行自动解析的语言,即不需要this关键字的语言的人来说,这可能有点令人惊讶)。

The this keyword refers to the instantiated object. this关键字指的是实例化的对象。 Not the class.不是班级。 Therefore you cannot normally use this inside static functions since the static function is not bound to any object but is bound directly to the class.因此,您通常不能在静态函数中使用this ,因为静态函数没有绑定到任何对象,而是直接绑定到类。

That is to say, in the following code:也就是说,在下面的代码中:

class A {
    static foo () {}
}

You cannot do:你不能这样做:

var a = new A();
a.foo() // NOPE!!

instead you need to call it as:相反,您需要将其称为:

A.foo();

Therefore, the following code would result in an error:因此,以下代码将导致错误:

class A {
    static foo () {
        this.bar(); // you are calling this as static
                    // so bar is undefinned
    }
    bar () {}
}

To fix it you can make bar either a regular function or a static method:要修复它,您可以将bar设为常规函数或静态方法:

function bar1 () {}

class A {
    static foo () {
        bar1();   // this is OK
        A.bar2(); // this is OK
    }

    static bar2 () {}
}

You can definitely do this, by returning an Immediately Invoked Async Function Expression from the constructor.您绝对可以通过从构造函数返回一个立即调用的异步函数表达式来做到这一点。 IIAFE is the fancy name for a very common pattern that was required in order to use await outside of an async function, before top-level await became available: IIAFE是一个非常常见的模式的花哨名称,它是在顶级 await可用之前在异步函数之外使用await所必需的:

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

We'll be using this pattern to immediately execute the async function in the constructor, and return its result as this :我们将使用这种模式在构造函数中立即执行异步函数,并将其结果返回为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); })();

To instantiate the class, use:要实例化类,请使用:

const instance = await new AsyncConstructor(...);

For TypeScript, you need to assert that the type of the constructor is the class type, rather than a promise returning the class type:对于 TypeScript,您需要断言构造函数的类型是类类型,而不是返回类类型的承诺:

class AsyncConstructor {
  constructor(value) {
    return (async (): Promise<AsyncConstructor> => {
      // ...
      return this;
    })() as unknown as AsyncConstructor;  // <-- type assertion
  }
}

Downsides缺点

  1. Extending a class with an async constructor will have a limitation.使用异步构造函数扩展类会有限制。 If you need to call super in the constructor of the derived class, you'll have to call it without await .如果您需要在派生类的构造函数中调用super ,则必须在没有await的情况下调用它。 If you need to call the super constructor with await , you'll run into TypeScript error 2337: Super calls are not permitted outside constructors or in nested functions inside constructors.如果您需要使用await调用超级构造函数,您将遇到 TypeScript 错误 2337: Super calls are not permitted outside constructors or in nested functions inside constructors.
  2. It's been argued that it's a "bad practice" to have a constructor function return a Promise .有人认为让构造函数返回 Promise是一种“不好的做法”。

Before using this solution, determine whether you'll need to extend the class, and document that the constructor must be called with await .在使用此解决方案之前,请确定您是否需要扩展类,并记录必须使用await调用构造函数。

Because async functions are promises, you can create a static function on your class which executes an async function which returns the instance of the class:因为异步函数是承诺,你可以在你的类上创建一个静态函数,它执行一个返回类实例的异步函数:

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

Call with let yql = await Yql.init() from an async function.从异步函数调用let yql = await Yql.init()

Unlike others have said, you can get it to work.不像其他人说的那样,你可以让它工作。

JavaScript class es can return literally anything from their constructor , even an instance of another class. JavaScript class es 可以从它们的constructor返回任何东西,甚至是另一个类的实例。 So, you might return a Promise from the constructor of your class that resolves to its actual instance.所以,你可能会从你的类的构造函数返回一个Promise ,它解析为它的实际实例。

Below is an example:下面是一个例子:

export class Foo {

    constructor() {

        return (async () => {

            // await anything you want

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

Then, you'll create instances of Foo this way:然后,您将以这种方式创建Foo的实例:

const foo = await new Foo();

The stopgap solution权宜之计

You can create an async init() {... return this;} method, then instead do new MyClass().init() whenever you'd normally just say new MyClass() .您可以创建一个async init() {... return this;}方法,然后在通常只说new MyClass() new MyClass().init() ) 。

This is not clean because it relies on everyone who uses your code, and yourself, to always instantiate the object like so.这并不干净,因为它依赖于使用您的代码的每个人以及您自己,总是像这样实例化对象。 However if you're only using this object in a particular place or two in your code, it could maybe be fine.但是,如果您仅在代码中的一两个特定位置使用此对象,则可能没问题。

A significant problem though occurs because ES has no type system, so if you forget to call it, you've just returned undefined because the constructor returns nothing.但是,由于 ES 没有类型系统,因此出现了一个重大问题,因此如果您忘记调用它,您只是返回了undefined ,因为构造函数没有返回任何内容。 Oops.哎呀。 Much better would be to do something like:更好的是做类似的事情:

The best thing to do would be:最好的办法是:

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}

The factory method solution (slightly better)工厂方法解决方案(稍微好一点)

However then you might accidentally do new AsyncOnlyObject, you should probably just create factory function that uses Object.create(AsyncOnlyObject.prototype) directly:但是你可能不小心做了新的 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):但是,如果您想在许多对象上使用此模式...您可以将其抽象为装饰器或您(详细地说,呃)在定义后调用的东西postProcess_makeAsyncInit(AsyncOnlyObject) ,但在这里我将使用extends因为它排序适合子类语义(子类是父类+额外的,因为它们应该遵守父类的设计契约,并且可以做额外的事情;如果父类也不是异步的,异步子类会很奇怪,因为它可以不以相同的方式初始化):


Abstracted solution (extends/subclass version)抽象解决方案(扩展/子类版本)

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}

(do not use in production: I have not thought through complicated scenarios such as whether this is the proper way to write a wrapper for keyword arguments.) (不要在生产中使用:我没有考虑过复杂的场景,例如这是否是为关键字参数编写包装器的正确方法。)

Based on your comments, you should probably do what every other HTMLElement with asset loading does: make the constructor start a sideloading action, generating a load or error event depending on the result.根据您的评论,您可能应该做所有其他带有资产加载的 HTMLElement 所做的事情:使构造函数启动侧加载操作,根据结果生成加载或错误事件。

Yes, that means using promises, but it also means "doing things the same way as every other HTML element", so you're in good company.是的,这意味着使用 Promise,但这也意味着“以与所有其他 HTML 元素相同的方式做事”,所以你是一个很好的伙伴。 For instance:例如:

var img = new Image();
img.onload = function(evt) { ... }
img.addEventListener("load", evt => ... );
img.onerror = function(evt) { ... }
img.addEventListener("error", evt => ... );
img.src = "some url";

this kicks off an asynchronous load of the source asset that, when it succeeds, ends in onload and when it goes wrong, ends in onerror .这将启动源资产的异步加载,当它成功时,以onload结束,当它出错时,以onerror结束。 So, make your own class do this too:所以,让你自己的班级也这样做:

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

And then you make the renderLoaded/renderError functions deal with the event calls and shadow dom:然后让 renderLoaded/renderError 函数处理事件调用和影子 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'));
  }

Also note I changed your id to a class , because unless you write some weird code to only ever allow a single instance of your <e-mail> element on a page, you can't use a unique identifier and then assign it to a bunch of elements.另请注意,我将您的id更改为class ,因为除非您编写一些奇怪的代码以仅允许页面上的<e-mail>元素的单个实例,否则您不能使用唯一标识符然后将其分配给一堆元素。

I usually prefer a static async method that returns a new instance, but here's another way to do it.我通常更喜欢返回新实例的静态异步方法,但这是另一种方法。 It's closer to literally awaiting a constructor.它更接近于字面上等待构造函数。 It works with TypeScript.它适用于 TypeScript。

class Foo {
  #promiseReady;

  constructor() {
    this.#promiseReady = this.#init();
  }

  async #init() {
    await someAsyncStuff();
    return this;

  }

  ready() {
    return this.promiseReady;
  }
}
let foo = await new Foo().ready();

I made this test-case based on @Downgoat's answer.我根据@Downgoat 的回答制作了这个测试用例。
It runs on NodeJS.它在 NodeJS 上运行。 This is Downgoat's code where the async part is provided by a setTimeout() call.这是 Downgoat 的代码,其中异步部分由setTimeout()调用提供。

'use strict';
const util = require( 'util' );

class AsyncConstructor{

  constructor( lapse ){
    this.qqq = 'QQQ';
    this.lapse = lapse;
    return ( async ( lapse ) => {
      await this.delay( lapse );
      return this;
    })( lapse );
  }

  async delay(ms) {
    return await new Promise(resolve => setTimeout(resolve, ms));
  }

}

let run = async ( millis ) => {
  // Instatiate with await, inside an async function
  let asyncConstructed = await new AsyncConstructor( millis );
  console.log( 'AsyncConstructor: ' + util.inspect( asyncConstructed ));
};

run( 777 );

My use case is DAOs for the server-side of a web application.我的用例是用于 Web 应用程序服务器端的 DAO。
As I see DAOs, they are each one associated to a record format, in my case a MongoDB collection like for instance a cook.正如我所看到的 DAO,它们每个都与记录格式相关联,在我的例子中是一个 MongoDB 集合,例如厨师。
A cooksDAO instance holds a cook's data. CooksDAO 实例保存厨师的数据。
In my restless mind I would be able to instantiate a cook's DAO providing the cookId as an argument, and the instantiation would create the object and populate it with the cook's data.在我焦躁不安的头脑中,我将能够实例化一个厨师的 DAO,提供 cookId 作为参数,并且实例化将创建对象并用厨师的数据填充它。
Thus the need to run async stuff into the constructor.因此需要在构造函数中运行异步的东西。
I wanted to write:我想写:

let cook = new cooksDAO( '12345' );  

to have available properties like cook.getDisplayName() .拥有可用的属性,如cook.getDisplayName()
With this solution I have to do:有了这个解决方案,我必须这样做:

let cook = await new cooksDAO( '12345' );  

which is very similar to the ideal.这与理想非常相似。
Also, I need to do this inside an async function.另外,我需要在async函数中执行此操作。

My B-plan was to leave the data loading out of the constructor, based on @slebetman suggestion to use an init function, and do something like this:我的 B 计划是根据 @slebetman 建议使用 init 函数将数据加载留在构造函数之外,并执行以下操作:

let cook = new cooksDAO( '12345' );  
async cook.getData();

which doesn't break the rules.这不违反规则。

Use the async method in constructor???在构造函数中使用异步方法???

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

If you can avoid extend , you can avoid classes all together and use function composition as constructors .如果你可以避免extend ,你可以一起避免类并使用函数组合作为构造函数 You can use the variables in the scope instead of class members:您可以使用范围内的变量而不是类成员:

async function buildA(...) {
  const data = await fetch(...);
  return {
    getData: function() {
      return data;
    }
  }
}

and simple use it as并简单地将其用作

const a = await buildA(...);

If you're using typescript or flow, you can even enforce the interface of the constructors如果您使用的是 typescript 或 flow,您甚至可以强制执行构造函数的接口

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...

Variation on the builder pattern, using call():构建器模式的变体,使用 call():

function asyncMethod(arg) {
    function innerPromise() { return new Promise((...)=> {...}) }
    innerPromise().then(result => {
        this.setStuff(result);
    }
}

const getInstance = async (arg) => {
    let instance = new Instance();
    await asyncMethod.call(instance, arg);
    return instance;
}

I found myself in a situation like this and ended up using an IIFE我发现自己处于这样的情况并最终使用了 IIFE

// using TypeScript

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

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

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

If you have other tasks that are dependant on the async task you can run them after the IIFE completes its execution.如果您有其他任务依赖于异步任务,您可以在 IIFE 完成执行后运行它们。

您可以在此处查看异步构造函数的代理机制 - https://dev.to/bakasura980/comment/1l379

A lot of great knowledge here and some super() thoughtful responses.这里有很多很棒的知识和一些 super() 深思熟虑的回应。 In short the technique outlined below is fairly straightforward, non-recursive, async-compatible and plays by the rules.简而言之,下面概述的技术相当简单、非递归、异步兼容并且符合规则。 More importantly I don't believe it has been properly covered here yet - though please correct me if wrong!更重要的是,我不相信它已经被正确地覆盖在这里 - 虽然如果有错请纠正我!

Instead of method calls we simply assign an II(A)FE to an instance prop:我们简单地将 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());
  }
}

Seems simple enough, how is it used?看起来很简单,它是如何使用的?

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

Before posting I had a brief discussion on the viability of this technique in the comments of @slebetman's comprehensive answer .在发布之前,我在@slebetman 的综合回答的评论中简要讨论了这种技术的可行性。 I wasn't entirely convinced by the outright dismissal, so thought I would open it up to further debate / tear down.我并不完全相信直接解雇,所以我想我会打开它以进行进一步的辩论/拆除。 Please do your worst :)请尽你最大的努力:)

You can use Proxy's construct handle to do this, the code like this:您可以使用 Proxy 的construct句柄来执行此操作,代码如下:

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

That can be done.那是可以做到的。
A simple code:一个简单的代码:

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

Or same as above but with real delays added, with setTimeout或者与上面相同,但添加了真正的延迟,使用 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();

And here a piece from my code in real life, with async image loading:这是我在现实生活中的一段代码,带有异步图像加载:

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

You may immediately invoke an anonymous async function that returns message and set it to the message variable.您可以立即调用返回消息的匿名异步函数并将其设置为消息变量。 You might want to take a look at immediately invoked function expressions (IEFES), in case you are unfamiliar with this pattern.如果您不熟悉此模式,您可能需要查看立即调用函数表达式 (IEFES)。 This will work like a charm.这将像一个魅力。

var message = (async function() { return await grabUID(uid) })()

You should add then function to instance.您应该将then函数添加到实例。 Promise will recognize it as a thenable object with Promise.resolve automatically Promise将使用Promise.resolve自动将其识别为 thenable 对象

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

The other answers are missing the obvious.其他答案缺少显而易见的。 Simply call an async function from your constructor:只需从构造函数中调用异步函数:

constructor() {
    setContentAsync();
}

async setContentAsync() {
    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>
    `
}

@slebetmen's accepted answer explains well why this doesn't work. @slebetmen 接受的答案很好地解释了为什么这不起作用。 In addition to the two patterns presented in that answer, another option is to only access your async properties through a custom async getter.除了该答案中提供的两种模式之外,另一种选择是仅通过自定义异步 getter 访问您的异步属性。 The constructor() can then trigger the async creation of the properties, but the getter then checks to see if the property is available before it uses or returns it.然后,constructor() 可以触发属性的异步创建,但 getter 会在使用或返回之前检查该属性是否可用。

This approach is particularly useful when you want to initialize a global object once on startup, and you want to do it inside a module.当你想在启动时初始化一个全局对象,并且你想在一个模块中完成它时,这种方法特别有用。 Instead of initializing in your index.js and passing the instance in the places that need it, simply require your module wherever the global object is needed.无需在index.js中进行初始化并将实例传递到需要它的地方,只需在需要全局对象的地方require模块即可。

Usage用法

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

Implementation执行

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

The closest you can get to an asynchronous constructor is by waiting for it to finish executing if it hasn't already in all of its methods:最接近异步构造函数的方法是等待它完成执行,如果它还没有在它的所有方法中执行:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM