241

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.

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

Alexander Craggs
  • 5,436
  • 4
  • 18
  • 38
  • 7
    A constructor's purpose is to allocate you an object and then return immediately. Can you be a lot more specific on *exactly why* do you think your constructor should be async? Because we're almost guaranteed dealing with an [XY problem](https://meta.stackexchange.com/a/66378) here. – Mike 'Pomax' Kamermans Apr 15 '17 at 21:50
  • 6
    @Mike'Pomax'Kamermans That is quite possible. Basically, I need to query a database in order to get the metadata required to load this element. Querying the database is an asynchronous operation, and hence I require some way of waiting for this to be completed before constructing the element. I would rather not use callbacks, as I've used await/async throughout the rest of the project and would like to keep continuity. – Alexander Craggs Apr 15 '17 at 21:56
  • @Mike'Pomax'Kamermans The full context of this is an email client, where each HTML element looks similar to `` and from there is populated with information using the `customElements.define()` method. – Alexander Craggs Apr 15 '17 at 21:57
  • 2
    You pretty much don't want a constructor to be async. Create a synchronous constructor that returns your object and then use a method like `.init()` to do the async stuff. Plus, since you're sublcass HTMLElement, it is extremely likely that the code using this class has no idea it's an async thing so you're likely going to have to look for a whole different solution anyway. – jfriend00 Apr 15 '17 at 23:15
  • @PopeyGilbert put those details in your post (don't add them as an "edit", just work them into the question naturally). In the mean time, answer incoming. – Mike 'Pomax' Kamermans Apr 16 '17 at 04:34
  • 1
    Relevant: [Is it bad practice to have a constructor function return a Promise?](https://stackoverflow.com/q/24398699/1048572) – Bergi Aug 07 '17 at 17:19

15 Answers15

356

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. So a function marked with async will return a 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. You can't use promises in a constructor because a constructor must return the object to be constructed, not a promise.

There are two design patterns to overcome this, both invented before promises were around.

  1. Use of an init() function. This works a bit like jQuery's .ready(). The object you create can only be used inside it's own init or ready function:

    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. 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:

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

The this keyword refers to the instantiated object. 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.

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:

function bar1 () {}

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

    static bar2 () {}
}
slebetman
  • 93,070
  • 18
  • 116
  • 145
  • 1
    note that based on the comments, the idea is that this is an html element, which typically doesn't have a manual `init()` but has the functionality tied to some specific attribute like `src` or `href` (and in this case, `data-uid`) which means using a setter that both binds and kicks off the init every time a new value is bound (and possibly during construction, too, but of course without waiting on the resulting code path) – Mike 'Pomax' Kamermans Apr 16 '17 at 17:22
  • You should comment on why the below answer is insufficient (if it is). Or address it otherwise. – Augie Gardner Apr 30 '19 at 21:15
  • I'm curious why `bind` is required in the first example `callback.bind(this)();`? So that you can do things like `this.otherFunc()` within the callback? – Alexander Craggs May 15 '19 at 15:56
  • 1
    @AlexanderCraggs It's just convenience so that `this` in the callback refers to `myClass`. If you always use `myObj` instead of `this` you don't need it – slebetman May 16 '19 at 00:39
  • 2
    Currently is a limitation on the language but I don't see why in the future you can't have `const a = await new A()` in the same way we have regular functions and async functions. – 7ynk3r Jul 23 '19 at 20:18
  • @7ynk3r As I explained, it's a fundamental mismatch between returning promises and returning a newly constructed object. There is no current recommendation for async/await to work on anything other than Promises so there is currently no future for such a thing. The OP didn't ask about a theoretical future of how such a function may work but how to do it in his code now. My answer isn't simply that it can't be done but I explicitly state that it **can** be done using other design patterns and I demonstrate how to do it (hence solve his coding problem)... – slebetman Jul 23 '19 at 23:13
  • ... theoretical questions such as how such a thing can be implemented does not belong on stackoverflow (will likely be marked as off-topic) but is perfectly fine on cs.stackexchange – slebetman Jul 23 '19 at 23:14
  • I don't see how this is strictly impossible. Async functions still ultimately return, it's just deferred. Async functions can happily return just as normal functions, you only have to wait for it. There's no fundamental mismatch. As you see below, someone already solved it. – jgmjgm Sep 25 '19 at 15:27
  • I prefer the `build()` approach as it forces a one-line. I was previously using the `init()` approach and hated the disconnectedness. However with `build()` its not great that you have to resolve the promise to get the object. Makes you write `const anInstance; MyClass.build().then(obj => anInstancee = obj)`. No complaints as it is the way. I'm just giving my thoughts about it. – HankCa Jan 10 '20 at 03:00
  • It wouldn't be so bad if you could do this, but then that returns a promise also! `const anInstance = MyClass.build().then(edg => edg);` – HankCa Jan 10 '20 at 03:03
  • You could of course do this, but not in a constructor :) `const anInstance = await MyClass.build()` – HankCa Jan 10 '20 at 03:05
  • I have a compact solution using `init()` I'll post as an answer. However it too can't be called in a constructor. – HankCa Jan 10 '20 at 03:16
  • How can I create only one instance from `myClass ` and share it to other files using `export default await new myClass();` ? this does not work as we can only use `await` inside `async` function. But in my example there is no function. – Kasra Sep 28 '20 at 20:26
189

You can definitely do this. Basically:

class AsyncConstructor {
    constructor() {
        return (async () => {

            // All async code here
            this.value = await asyncFunction();

            return this; // when done
        })();
    }
}

to create the class use:

let instance = await new AsyncConstructor();

This solution has a few short falls though:

super note: If you need to use super, you cannot call it within the async callback.

TypeScript note: this causes issues with TypeScript because the constructor returns type Promise<MyClass> instead of MyClass. There is no definitive way to resolve this that I know of. One potential way suggested by @blitter is to put /** @type {any} */ at the beginning of the constructor body— I do not know if this works in all situations however.

Downgoat
  • 11,422
  • 5
  • 37
  • 64
  • Is there any particular reason to return this; and return the value returned by the closure (so the this)? I've tested with Node TS2.9.2 and Node10.8, and it works pretty well without any returns. – PAStheLoD Aug 14 '18 at 20:32
  • 1
    @PAStheLoD I don’t think it’ll resolve to the object without the return however you are saying that it does so I’ll review spec and update... – Downgoat Aug 14 '18 at 22:19
  • I'm trying this approach because it pollutes minimally the instantiation, requiring only to prepend an `await` to the new. But I'm having trouble passing an argument to the async part. I have `constructor( x )` and want to do `return (async ( x ) => {`. I don't know how to pass the "x", would like to have some more help. – Juan Lanus Sep 19 '18 at 16:11
  • 2
    @JuanLanus the async block will automatically capture the parameters so for argument x you only need to do `constructor(x) { return (async()=>{await f(x); return this})() }` – Downgoat Sep 19 '18 at 16:13
  • That was fast Downgoat, thanks! It was what I was doing, no success. Now I'm trying to set the parameter in the IIFE, thus: ` })( x );` and it seems to work better. I'm building a test case to isolate the pertinent code and will come back if a solution arises. – Juan Lanus Sep 19 '18 at 16:27
  • @Downgoat: Hi Vihan, I wrote a test-case based on your answer and published it in this thread https://stackoverflow.com/questions/43431550/async-await-class-constructor#answer-52431905 – Juan Lanus Sep 20 '18 at 19:24
  • How can you use super() in a child class which has an async constructor? Is there any solution out there? To be more specific: also the parent class has an async constructor. – user2912903 Dec 30 '18 at 20:25
  • @user2912903 that is the one shortfall... can't figure out an idiomatic way to do this because super() unfortunately doesn't return what the parent constructor returns. Best workaround is to have like some init() method that the async superclass calls and override that in subclass – Downgoat Dec 30 '18 at 22:11
  • 2
    @PAStheLoD: `return this` is necessary, because while `constructor` does it automatically for you, that async IIFE does not, and you'll end up returning an empty object. – Dan Dascalescu Jun 29 '19 at 21:30
  • 1
    Currently as of TS 3.5.1 targeting ES5, ES2017, ES2018 (and probably others, but I haven't checked) if you do a return in a constructor you get this error message: "Return type of constructor signature must be assignable to the instance type of the class." The type of the IIFE is a Promise, and since the class is not a Promise, I don't see how it could work. (What could you return other than 'this'?) So this means both returns are unnecessary. (The outer one being a bit worse, since it leads to a compile error.) – PAStheLoD Jun 30 '19 at 12:52
  • 3
    @PAStheLoD yeah, that's a typescript limitation. Typically in JS a class `T` should return `T` when constructed but to get the async ability we return `Promise` which resolves to `this`, but that confuses typescript. You do need the outer return otherwise you won't know when the promise finishes completing— as a result this approach won't work on TypeScript (unless there's some hack with perhaps type aliasing?). Not a typescript expert though so can't speak to that – Downgoat Jul 01 '19 at 19:06
  • This code causes a lot of complaints from VSCode's strict TypeScript style checker (can be turned on even for plain JS, it's fun!). The following code avoids red zig-zag lines: `constructor(x) { /** @type {any} */ const prom = (async(x) => { await f(x); return this; })(x); return prom; }` – blitter Dec 27 '19 at 01:12
  • @blitter that didn't work for me (typescript 3.5.3) - compile error. Bummer :-( – HankCa Jan 10 '20 at 02:02
  • Right it’s quite possible that this just makes the style checker in the VSCode editor happy, but could very well fail at TS compile time. I don’t use TS so, can’t help. – blitter Apr 12 '20 at 00:46
  • typescript issue: https://github.com/microsoft/TypeScript/issues/38519 – Yukulélé Jun 10 '20 at 19:56
  • How can I create only one instance from `AsyncConstructor ` and share it to other files using `export default await new AsyncConstructor();` ? this does not work as we can only use `await` inside `async` function. But in my example there is no function. – Kasra Sep 28 '20 at 20:24
  • I tried doing this but not working for me. Can anyone guide me please. Here is my code https://gist.github.com/vipulwairagade/a77bdd85909d61e3f7d87f2dcc57f2b1 – Vipulw Sep 29 '20 at 14:25
  • @Vipulw `await new ConnectwiseRestApi(options).ServiceDeskAPI` => `(await new ConnectwiseRestApi(options)).ServiceDeskAPI – Downgoat Sep 29 '20 at 21:57
  • @Downgoat Thank you so much this idea worked. I had one more method call attached to it so had to add one more await outside parenthesis too. Thanks – Vipulw Sep 30 '20 at 11:08
  • 1
    For TypeScript, I simply put `// @ts-ignore` before the return statement – Willem Mulder Oct 08 '20 at 07:57
  • I just upvoted your answer, @Downgoat, for correctness after one user made me notice I came up with the same solution as yours and posted an answer that's pretty much identical to yours. – Davide Cannizzo Oct 12 '20 at 14:28
  • Way late to the party and not interested enough to read through all of the comments, but why can't you just pass `super` as a parameter to the anonymous function? You may need to name it differently and the semantics may be slightly different or require local closure but it's a scoped variable which should be able to be passed through to the child. – M.Babcock May 29 '21 at 01:02
8

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.

Vidar
  • 645
  • 9
  • 13
7

Unlike others have said, you can get it to work.

JavaScript classes can return literally anything from their constructor, even an instance of another class. So, you might return a Promise from the constructor of your class that resolves to its actual instance.

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:

const foo = await new Foo();
Davide Cannizzo
  • 1,974
  • 1
  • 19
  • 27
  • 1
    The argument of `call` gets ignored since it's an arrow function. – Robert Oct 12 '20 at 01:57
  • You're right, @Robert. It was my fault. I'll update my answer in a while — replacing the `.call(this)` call with a normal function invocation should be fine. Thanks for pointing that out – Davide Cannizzo Oct 12 '20 at 13:09
  • 1
    Seems the same as [Downgoat](https://stackoverflow.com/a/50885340/5459839) answered more than half a year before you. I don't see the point of reposting the same idea. – trincot Oct 12 '20 at 13:16
  • 1
    I'm sorry, @trincot, for posting the same thing over. I didn't notice Downgoat's answer when writing mine. I hope my answer won't mess up this whole StackOverflow question ;) I respect your downvote, even though I don't fully understand how it will be helpful to the community, since my behavior wasn't intentional. – Davide Cannizzo Oct 12 '20 at 14:25
6

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

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. 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:

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


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

ninjagecko
  • 77,349
  • 22
  • 129
  • 137
5

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.

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. 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. So, make your own class do this too:

class EMailElement extends HTMLElement {
  constructor() {
    super();
    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
    let getEmail = new Promise( (resolve, reject) => {
      yourDataBase.getByUID(uid, (err, result) => {
        if (err) return reject(err);
        resolve(result);
      });
    });
    // kick off the promise, which will be async all on its own
    getEmail()
    .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(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.

Mike 'Pomax' Kamermans
  • 40,046
  • 14
  • 84
  • 126
2

I made this test-case based on @Downgoat's answer.
It runs on NodeJS. This is Downgoat's code where the async part is provided by a setTimeout() call.

'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.
As I see DAOs, they are each one associated to a record format, in my case a MongoDB collection like for instance a cook.
A cooksDAO instance holds a cook's data.
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.
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().
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.

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:

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

which doesn't break the rules.

Juan Lanus
  • 2,037
  • 22
  • 15
2

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());
    });
}
CodeChanger
  • 6,296
  • 1
  • 33
  • 66
1

If you can avoid extend, you can avoid classes all together and use function composition as constructors. 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

Interface A {
  getData: object;
}

async function buildA0(...): Promise<A> { ... }
async function buildA1(...): Promise<A> { ... }
...
7ynk3r
  • 850
  • 12
  • 16
0

Variation on the builder pattern, using 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;
}
Jeff Lowery
  • 1,953
  • 24
  • 34
0

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. This will work like a charm.

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

@slebetmen's accepted answer explains well why this doesn't work. In addition to the two patterns presented in that answer, another option is to only access your async properties through a custom async 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.

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.

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;
    }
  }
}
Neal
  • 1,769
  • 1
  • 10
  • 14
-1

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
    }
}
Richie Bendall
  • 3,783
  • 2
  • 24
  • 36
-2

You should add then function to instance. Promise will recognize it as a thenable object with Promise.resolve automatically

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)
}
吴浩锋
  • 11
  • 1
  • `innerResolve(this)` will not work, as `this` is still a thenable. This leads to a never-ending recursive resolution. – Bergi Dec 04 '17 at 11:09
-3

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>
    `
}
Navigateur
  • 1,368
  • 2
  • 17
  • 34
  • 2
    Like [another "obvious" answer here](https://stackoverflow.com/a/47611907/1269037), this one won't do what the programmer commonly expects of a constructor, i.e. that the content is set when the object is created. – Dan Dascalescu Jan 04 '18 at 05:31
  • 2
    @DanDascalescu It is set, just asynchronously, which is exactly what the questioner requires. Your point is that the content is not set synchronously when the object is created, which is not required by the question. That's why the question is about using await/async from within a constructor. I've demonstrated how you can invoke as much await/async as you want from a constructor by calling an async function from it. I've answered the question perfectly. – Navigateur Jan 05 '18 at 07:51
  • @Navigateur that was the same solution I came up with, but the [comments on another similar question](https://stackoverflow.com/questions/49694779/calling-an-async-function-in-the-constructor) suggest it should not be done this way. The main problem being a promise is lost in the constructor, and this is antipattern. Do you have any references where it recommends this approach of calling an async function from your constructor? – Marklar Oct 25 '18 at 00:59
  • 1
    @Marklar no references, why do you need any? It doesn't matter if something is "lost" if you don't need it in the first place. And if you do need the promise, it's trivial to add `this.myPromise =` (in the general sense) so not an anti-pattern in any sense whatsoever. There are perfectly valid cases for needing to kick off an async algorithm, upon construction, that has no return value itself, and adding one us simple anyway, so whoever is advising not to do this is misunderstanding something – Navigateur Oct 29 '18 at 02:27
  • 1
    Thanks for taking the time to reply. I was looking for further reading because of the conflicting answers here on Stackoverflow. I was hoping to confirm some of the best practices for this scenario. – Marklar Oct 29 '18 at 23:50