[javascript] 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.

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

This question is related to javascript node.js async-await

The answer is


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


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

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.


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

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

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.


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

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.


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

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

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

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

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.


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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to node.js

Hide Signs that Meteor.js was Used Querying date field in MongoDB with Mongoose SyntaxError: Cannot use import statement outside a module Server Discovery And Monitoring engine is deprecated How to fix ReferenceError: primordials is not defined in node UnhandledPromiseRejectionWarning: This error originated either by throwing inside of an async function without a catch block dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib error running php after installing node with brew on Mac internal/modules/cjs/loader.js:582 throw err DeprecationWarning: Buffer() is deprecated due to security and usability issues when I move my script to another server Please run `npm cache clean`

Examples related to async-await

How to correctly write async method? How can I use async/await at the top level? Any difference between await Promise.all() and multiple await? Async/Await Class Constructor Syntax for async arrow function try/catch blocks with async/await Using filesystem in node.js with async / await Use async await with Array.map Using await outside of an async function SyntaxError: Unexpected token function - Async Await Nodejs