Async/Await Class Constructor
Asked Answered
S

22

424

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

Slattery answered 15/4, 2017 at 21:41 Comment(8)
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 here.State
@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.Slattery
@Mike'Pomax'Kamermans The full context of this is an email client, where each HTML element looks similar to <e-mail data-uid="1028"></email> and from there is populated with information using the customElements.define() method.Slattery
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.Postimpressionism
@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.State
Relevant: Is it bad practice to have a constructor function return a Promise?Bamford
There is a stage 1 proposal called Async Initialization which proposes syntax such as async constructor(){} inside classes, and even async class.Megaspore
I’ve been thinking about this lately, because I’m working on a project where classes are instantiated using user-settable definitions from dynamically imported modules. This introduces asynchronicity everywhere, so something like async constructor(){ this.property = (await import(theModule)).theMethod(); } would be nice. But I realized that modules serve as a nice analogy of singleton classes with an async constructor. After all, they allow top-level await, and because they only run once, they’ve been referred to as “singletons” before.Megaspore
P
542

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 its 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)();
        }
    }
  1. 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 () {}
}
Papeete answered 16/4, 2017 at 4:41 Comment(24)
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)State
You should comment on why the below answer is insufficient (if it is). Or address it otherwise.Aggi
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?Slattery
@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 itPapeete
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.Exertion
@Exertion 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)...Papeete
... 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.stackexchangePapeete
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.Guberniya
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.Sarcenet
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);Sarcenet
You could of course do this, but not in a constructor :) const anInstance = await MyClass.build()Sarcenet
I have a compact solution using init() I'll post as an answer. However it too can't be called in a constructor.Sarcenet
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.Rutty
@slebetman: this does actually work - see the runnable snippet in the other highly upvoted answer. Can you please update or delete this now incorrect answer (for that special badge) in light of this? Thank you.Moyer
@DanDascalescu that snippet only works in combination with await, instantly breaking the regular invocation, thus making it not a proper solution but more of a hack. See Bergi's Is it bad practice to have a constructor function return a Promise? but the tldr is that constructors should only ever return instances, because otherwise they're violating the spec.State
Old question and answer .. but could you not just include an IIAFE against a property in the constructor (returning this)? E.g: class X { constructor() { this.ready = (async () => this)() }} which can be used like const x = await new X().ready OR const x = new X(); await x.ready OR [...] the IIAFE will always run immediately, and the ready promise can either be used at instantiation or any time in the future.Pegmatite
@Pegmatite You don't need an IIFE for that, this is always synchronous. However, the problem here is the OP wants some parts of the object to come from async sources (the message). So while x may return the correct object, x.something may not exist immediately after you do new X(). The two solutions above gives you mechanisms to hook to for executing your code at the right time. You can see these design patterns in libraries like jQuery or web3. Of course, if you return a different object in a constructor..Papeete
.. you will override what the constructor returns but wouldn't it be weird that when you call new X() you don't get an instance of X but instead you get an instance of Y (eg. a Promise)? So in general I wouldn't recommend overriding the return result of a constructor because it is not what other developers reading your code would expectPapeete
@Papeete I was mainly trying to remain async/await compatible (so no callbacks) and have the async component (e.g. the message) fetched on instantiation, inside the IIAFE attached to a property (e.g. this.ready). Yes, this is synchronous, but returning it in the IIAFE allows you to await on instantiation until e.g. message is ready. It also retains the expected behaviour (new X() returns a class, new X().ready returns a promise ... so x = await new X().ready works!). As mentioned I am possibly completely missing the point .. but I might add a better example below anyway :)Pegmatite
A more complete, albeit minified, example to copy into the console: class X { constructor() { this.ready = (async () => { this.msg = await X.msg; return this; })() } static get msg() { return fetch('data:,Hello%20World%21').then(e => e.text()) } } const x = await new X().ready; console.log(x.msg, await X.msg) or here's a better example with some breathing room.Pegmatite
@Pegmatite I don't think what you propose will work because your promise is not waiting for anything to resolve. You are synchronously returning this (which by design works - you can return synchronously but it will just wrap it in a Promise and wait exactly until next event loop). So the message might not be ready by then. What you want is already described in my answer: let x = await X.build(). It is better because it conforms to what programmers expect. Like I said, you can return a manually constructed Promise but it would be very weird if..Papeete
.. calling new X() creates an instance of Y. Still, javascript supports it if you want to do it, just not with your code. See Downgoat's answer below for a working example. I still don't think you should do it because, like I said.. it's not what programmers expect when using newPapeete
Hey @Papeete appreciate the discussion! I've gone ahead and just added an answer below that outlines how what I'm talking about is (a little) different to what you've proposed .. but same principles. Feel free to comment down there!Pegmatite
we could just redefine constructor as something that return the object, OR a promise to that object.Spaceband
B
331

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:

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

We'll be using this pattern to immediately execute the async function in the constructor, and return its result as 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:

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. 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.
  2. It's been argued that it's a "bad practice" to have a constructor function return a Promise.

Before using this solution, determine whether you'll need to extend the class, and document that the constructor must be called with await.

Bogy answered 16/6, 2018 at 5:51 Comment(13)
@Downgoat: Hi Vihan, I wrote a test-case based on your answer and published it in this thread #43432050Wives
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.Treadway
@Treadway 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 subclassBogy
typescript issue: github.com/microsoft/TypeScript/issues/38519Profligate
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.Rutty
I tried doing this but not working for me. Can anyone guide me please. Here is my code gist.github.com/vipulwairagade/a77bdd85909d61e3f7d87f2dcc57f2b1Stapler
@Stapler await new ConnectwiseRestApi(options).ServiceDeskAPI => `(await new ConnectwiseRestApi(options)).ServiceDeskAPIBogy
@Bogy 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. ThanksStapler
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.Compilation
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.Doscher
This is the same as making constructor async. Because in both cases you return a promise from the constructor. So the solution is in the async function to return this. So basically return promise of this is the same as return undefined or this. But return promise of anything else will return a promise from the constructor and you will never get actuall instance.Possie
Rather than immediately returning the IIAFE would you instead add it as a property that resolves to this ... which should alleviate most of the "downsides"? Maybe I'm missing something? Comment with example herePegmatite
This approach seems to fail with current typescript (esnext), the property this.value = value; shows the following error: Property 'value' does not exist on type 'AsyncConstructor'. and adding value:number; right below the class name and before constructor() also fails with Property 'value' has not initializer and is not definitely assigned in the constructor.Mumbletypeg
C
19

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();
Compilation answered 13/2, 2020 at 14:8 Comment(3)
The argument of call gets ignored since it's an arrow function.Vertical
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 outCompilation
Worked like a charm, obviously this will make the constructor return a promise so you will always need to await.Koziarz
B
17

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.

Bosky answered 22/5, 2018 at 22:3 Comment(1)
Isn't this the same build pattern that was described in 2017 by slebetman's answer?Moyer
W
11

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

Winola answered 8/9, 2019 at 13:19 Comment(0)
D
6

Many good (and some bad) notes in this thread... but none really covers the whole story and TypeScript. So here's my take.

There are 2 workarounds for this issue.

1. Use closures instead of classes:

async function makeAPI() {
  await youCanGoAsyncHere()

  async function fetchFirst() {}
  async function fetchSecond() {}

  return {
    fetchFirst,
    fetchSecond,
  }
}

It's messy to replicate some inheritance patterns with closures but for simpler cases it's often good enough.

2. Use factories with protected constructor and init:

import * as U from "lib/utils"

class API {
  data!: number // the use of ! here is fine 
  // we marked the constructor "protected" + we call `init` in `make` 
  // assuming we don't like multiple `data?.something` checks

  protected constructor() {
    ...
  }

  protected async init() {
    await youCanGoAsyncHere()
    this.data = 123 // assume other methods depend on this data
  }

  fetchFirst() {}
  fetchSecond() {}

  static async make() {
    const api = new API()
    await api.init()
    return api
  }
}

const t = await Test.make()
console.log(t.data)

The main downside here is that inheritance of static methods in JS/TS with generics is somewhat crippled.

Despoil answered 10/3, 2023 at 7:29 Comment(0)
U
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 {
  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(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.

Unaccomplished answered 16/4, 2017 at 4:57 Comment(0)
U
4

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.

class Foo {
  #promiseReady;

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

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

  }

  ready() {
    return this.#promiseReady;
  }
}
let foo = await new Foo().ready();
Unbuckle answered 21/6, 2021 at 16:14 Comment(0)
B
3

You can use Proxy's construct handle to do this, the code like this:

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
Borage answered 19/7, 2022 at 13:54 Comment(0)
P
2

A lot of great knowledge here and some super() thoughtful responses. 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:

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

Pegmatite answered 9/6, 2022 at 4:3 Comment(0)
W
1

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.

Wives answered 20/9, 2018 at 19:20 Comment(1)
I've revamped Downgoat's answer. Can you please check if this answer provides any thing extra now, and if not, kindly delete it? Thank you.Moyer
H
1

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());
    });
}
Hardnosed answered 21/3, 2019 at 6:2 Comment(2)
can someone change this to meaningful var names? what do qwe, q, w, rs and rj mean?Convertible
I would guess que and q are just placeholders for query functions on the DB. rs and rj stand for resolve and reject from the Promise Object: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Wear
E
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> { ... }
...
Exertion answered 23/7, 2019 at 20:32 Comment(0)
F
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;
}
Fix answered 27/3, 2018 at 23:8 Comment(0)
C
0

I found myself in a situation like this and ended up using an 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.

Cartridge answered 27/11, 2021 at 15:37 Comment(1)
This is almost the correct answer, which also uses an IIFE.Moyer
A
0
class EmailElement {
  constructor() {
    return new Promise((resolve) => setTimeout(resolve, 0, this));
  }
}

Use:

const main = async() => {
  const emailElement = await new EmailElement();
};
Ancon answered 18/4, 2023 at 16:22 Comment(0)
C
0

I have the same problem, i need to connect to database async, then get data from that class but makes sure database is already connected so in this is example of my basemodel:

import dbConnect from '@lib/dbConnect'

class BaseModel {
    async init(methodName, ...args){
        try {
            await dbConnect();
        } catch (error) {
            console.error('Error connecting to database:', error);
            throw new Error('Error connecting to database');
        }
        if (typeof this[methodName] === 'function') {
            return this[methodName](...args);
        } else {
            throw new Error(`Method ${methodName} not found`);
        }
    }
}
export default BaseModel;

and this is User as child class

// file : src\models\User.js
import BaseModel from '@base/BaseModel';
import mongoose from 'mongoose';
import bcryptjs from 'bcryptjs';

class User extends BaseModel {
    constructor() {
        this.table='users';
        try{
            if (mongoose.connection.models['User']) {
                delete mongoose.connection.models['User'];
            }
        }catch(Exception){}
        this.User = this.initModel();
    }
    initModel() {
        const userSchema = new mongoose.Schema({
            ... somevalue 
        });
        return mongoose.model('User', userSchema, this.table);
    }

    login({username,password}) {
        const user=this.findByUsername(username);
        if (user) {
            const passwordMatch =bcryptjs.compare(password, user.password);
            if (passwordMatch) {
                return user;
            } else {
                return Promise.reject(new Error("Invalid password"));
            }
        } else {
            return Promise.reject(new Error("User tidak diketemukan"));
        }
    }
}
export default User;

and call it this way:

const userModel=new User();
      return userModel.init("login",{username:credentials.username,password:credentials.password});

So, if you need to call function, you must call init first, syntax is:

userModel.init("Your class function name","your class arguments")

that's the way to make sure that database is connected asynchronously before you do something with database

Cheek answered 16/2 at 6:15 Comment(0)
C
-1

That can be done, like this:

class test
{
   constructor () { return Promise.resolve (this); }
}

Or with real delays added, any asynchronous event, or setTimeout for instance:

class test
{
   constructor ()
   {
      return new Promise ( (resolve, reject) =>
      {  //doing something really delayed
         setTimeout (resolve, 5, this);
      });
   }
   doHello(a) {console.log("hello: " + a);}
}
async function main()
{  
   new test().then(a=> a.doHello("then")); //invoking asynchronously
   console.log("testing"); //"testing" will be printed 5 seconds before "hello"
   (await new test()).doHello("await"); //invoking synchronously
}
main();

In some cases, when involving inheritance, can't be returned Promise from base class constructor, it will mess with this pointer. First idea is adding function then to make the class promise alike, but it is problematic. My workaround is using a private variable #p retaining the promise, and function _then (instead of then) which should act on completion of #p

class basetest 
{
   #p  = null;
   constructor (){this.#p = Promise.resolve(this);}
   async _then (func)
   {
      return this.#p.then ( (ths) =>
      {
         this.#p = null;
         if (func) func (ths);
         return this;
      });
   }
   doHello(a) {console.log("hello: " + a);}
}
class test extends basetest 
{
   constructor(context)
   {
      super(context);
      this.msg = "test: ";
   }
   doTest(a) {console.log(this.msg + a);}
}
async function main()
{
   (await new test()._then()).doHello("await");//synchronous call
   //note, now we use _then instead of then
   (new test())._then(a=>{a.doHello("then");a.doTest("then");} );//asynchronous call
}
main();

And suppose a some webgl, with async image loading, generating a mesh and drawing a vertex array object:

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);
            } );
         });
   }
///...
}
function drawVao(vao) {/*do something*/}
async function main()
{
   let vao = await new HeightMap ("./heightmaps/ArisonaCraterHeightMap.png");
   drawVao(vao);
///...
}
/*
//version of main with asynchronous call
async function main()
{
   new HeightMap ("./heightmaps/ArisonaCraterHeightMap.png").then (vao => drawVao(vao));
///...
}
*/
main();

Second variant with _then, as it really works in real life:

class HeightMap extends GlVAObject
{
   #vertices = [];
   constructor (src, crossOrigin = "")
   {
      //super(theContextSetup);
      let image = new Image();
      image.src = src;
      image.crossOrigin = crossOrigin;
      this.#p = Promise ( (resolve, reject) =>
         {
            image.addEventListener('load',  () =>
            {
               //...
               resolve(this);
            } );
         });
   }
   async _then (func)
   {
      return this.#p.then ( (ths) =>
      {
         this.#p = null;
         if (func) func (ths);
         return this;
      });
   }
///...
}
function drawVao(vao) {/*do something*/}
async function main()
{
   new HeightMap ("./heightmaps/ArisonaCraterHeightMap.png")._then(vao => drawVao(vao)  );
///...
}
/*
//version of main with synchronous call
async function main()
{
   vao = (await new HeightMap ("./heightmaps/ArisonaCraterHeightMap.png"))._then();
   drawVao(vao);
///...
}*/
main();
Convergence answered 26/1, 2023 at 14:57 Comment(0)
A
-2

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>
    `
}
Action answered 5/12, 2017 at 4:13 Comment(5)
Like another "obvious" answer here, 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.Moyer
@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.Action
@Action that was the same solution I came up with, but the comments on another similar question 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?Craig
@Craig 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 somethingAction
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.Craig
A
-3

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)
}
Aludel answered 4/12, 2017 at 10:45 Comment(1)
innerResolve(this) will not work, as this is still a thenable. This leads to a never-ending recursive resolution.Bamford
R
-3

@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;
    }
  }
}
Radian answered 29/8, 2018 at 18:56 Comment(1)
@slebetman's answer is incorrect, because this answer shows how async constructors work.Moyer
W
-3

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
    }
}
Witticism answered 5/10, 2020 at 4:22 Comment(1)
You can get much closer by actually using an async constructor.Moyer

© 2022 - 2024 — McMap. All rights reserved.