Javascript class constructor async

Async/Await Class Constructor

await: Note on calling functions inside static functions. Solution 3: 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: Call with from an async function.

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() shadowRoot.innerHTML = ` ` > >) 

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

Читайте также:  Определить тип файла питон

    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 >); 
class myClass < constructor () < >init (callback) < // do something async and call the callback: callback.bind(this)(); >> 
myClass.build().then(function(myObj) < // myObj is returned by the promise, // not by the constructor // or builder >); // with async/await: async function foo ()
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); >); > > 
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:

instead you need to call it as:

Therefore, the following code would result in an error:

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

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:

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=> < // . return this; >)() as unknown as AsyncConstructor; // > 

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 .

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.

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

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

Then, you’ll create instances of Foo this way:

Javascript — Async/Await Class Constructor, 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. Code sampleconstructor (async_param) >Feedback

Can async/await be used in constructors?

As the question stated. Will I be allowed to do this:

To expand upon what Patrick Roberts said, you cannot do what you are asking, but you can do something like this instead:

class MyClass < constructor() < //static initialization >async initialize() < await WhatEverYouWant(); >static async create() < const o = new MyClass(); await o.initialize(); return o; >> 

Then in your code create your object like this:

const obj = await MyClass.create(); 

Without trying to fortune-tell about future decisions, let’s concentrate on practicality and what is already known.

ES7, like ES6 before it will try to be a backwards compatible expansion to the language. With that in mind, a backwards compatible constructor function is essentially a regular function (with some runtime restrictions) that’s meant to be invoked with the new keyword. When that happens, the function’s return value gets special treatment, specifically, non-object return values are ignored and the newly allocated object is returned while object return values are returned as is (and the newly allocated object is thrown away). With that, your code would result in a promise being returned and no «object construction» would take place. I don’t see the practicality of this and I suppose if anybody takes the time to find what to do with such code it will be rejected.

  1. Constructor is a function that needs to provide a concrete object.
  2. Async returns a promise; exactly opposite of concreteness.
  3. async constructor is conceptually conflicting.

You can get a promise from the return value, and await on that:

class User < constructor() < this.promise = this._init() >async _init() < const response = await fetch('https://jsonplaceholder.typicode.com/users') const users = await response.json() this.user = users[Math.floor(Math.random() * users.length)] >>(async () < const user = new User() await user.promise return user >)().then(u => < $('#result').text(JSON.stringify(u.user, null, 2)) >).catch(err => < console.error(err) >)

Javascript — Can async/await be used in constructors?, To expand upon what Patrick Roberts said, you cannot do what you are asking, but you can do something like this instead: class MyClass < …

Asynchronous constructor

How can I best handle a situation like the following?

I have a constructor that takes a while to complete.

var Element = function Element(name)< this.name = name; this.nucleus = <>; this.load_nucleus(name); // This might take a second. > var oxygen = new Element('oxygen'); console.log(oxygen.nucleus); // Returns <>, because load_nucleus hasn't finished. 

I see three options, each of which seem out of the ordinary.

One , add a callback to the constructor.

var Element = function Element(name, fn)< this.name = name; this.nucleus = <>; this.load_nucleus(name, function()< fn(); // Now continue. >); > Element.prototype.load_nucleus(name, fn)< fs.readFile(name+'.json', function(err, data) < this.nucleus = JSON.parse(data); fn(); >); > var oxygen = new Element('oxygen', function()< console.log(oxygen.nucleus); >); 

Two , use EventEmitter to emit a ‘loaded’ event.

var Element = function Element(name)< this.name = name; this.nucleus = <>; this.load_nucleus(name); // This might take a second. > Element.prototype.load_nucleus(name)< var self = this; fs.readFile(name+'.json', function(err, data) < self.nucleus = JSON.parse(data); self.emit('loaded'); >); > util.inherits(Element, events.EventEmitter); var oxygen = new Element('oxygen'); oxygen.once('loaded', function()< console.log(this.nucleus); >); 

Or three , block the constructor.

var Element = function Element(name)< this.name = name; this.nucleus = <>; this.load_nucleus(name); // This might take a second. > Element.prototype.load_nucleus(name, fn) < this.nucleus = JSON.parse(fs.readFileSync(name+'.json')); >var oxygen = new Element('oxygen'); console.log(oxygen.nucleus) 

But I haven’t seen any of this done before.

What other options do I have?

Update 2: Here is an updated example using an asynchronous factory method. N.B. this requires Node 8 or Babel if run in a browser.

class Element < constructor(nucleus)< this.nucleus = nucleus; >static async createElement() < const nucleus = await this.loadNucleus(); return new Element(nucleus); >static async loadNucleus() < // do something async here and return it return 10; >> async function main() < const element = await Element.createElement(); // use your element >main(); 

Update: The code below got upvoted a couple of times. However I find this approach using a static method much better: https://stackoverflow.com/a/24686979/2124586

ES6 version using promises

class Element < constructor()< this.some_property = 5; this.nucleus; return new Promise((resolve) => < this.load_nucleus().then((nucleus) =>< this.nucleus = nucleus; resolve(this); >); >); > load_nucleus() < return new Promise((resolve) => < setTimeout(() =>resolve(10), 1000) >); > > //Usage new Element().then(function(instance)< // do stuff with your instance >); 

Given the necessity to avoid blocking in Node, the use of events or callbacks isn’t so strange (1) .

With a slight edit of Two, you could merge it with One:

var Element = function Element(name, fn)< this.name = name; this.nucleus = <>; if (fn) this.on('loaded', fn); this.load_nucleus(name); // This might take a second. > . 

Though, like the fs.readFile in your example, the core Node APIs (at least) often follow the pattern of static functions that expose the instance when the data is ready:

var Element = function Element(name, nucleus) < this.name = name; this.nucleus = nucleus; >; Element.create = function (name, fn) < fs.readFile(name+'.json', function(err, data) < var nucleus = err ? null : JSON.parse(data); fn(err, new Element(name, nucleus)); >); >; Element.create('oxygen', function (err, elem) < if (!err) < console.log(elem.name, elem.nucleus); >>); 

(1) It shouldn’t take very long to read a JSON file. If it is, perhaps a change in storage system is in order for the data.

I have developed an async constructor:

function Myclass() < return (async () =>< . code here . return this; >)(); > (async function() < let s=await new Myclass(); console.log("s",s) >)(); 
  • async returns a promise
  • arrow functions pass ‘this’ as is
  • it is possible to return something else when doing new (you still get a new empty object in this variable. if you call the function without new. you get the original this. like maybe window or global or its holding object).
  • it is possible to return the return value of called async function using await.
  • to use await in normal code, need to wrap the calls with an async anonymous function, that is called instantly. (the called function returns promise and code continues)

my 1st iteration was:

maybe just add a callback

call an anonymous async function, then call the callback.

function Myclass(cb) < var asynccode=(async () =>< await this.something1(); console.log(this.result) >)(); if(cb) asynccode.then(cb.bind(this)) > 

my 2nd iteration was:

let’s try with a promise instead of a callback. I thought to myself: strange a promise returning a promise, and it worked. .. so the next version is just a promise.

function Myclass() < this.result=false; var asynccode=(async () => < await new Promise (resolve =>setTimeout (()=>, 1000)) console.log(this.result) return this; >)(); return asynccode; > (async function() < let s=await new Myclass(); console.log("s",s) >)(); 

callback-based for old javascript

function Myclass(cb) < var that=this; var cb_wrap=function(data)getdata(cb_wrap) > new Myclass(function(s)< >); 

One thing you could do is preload all the nuclei (maybe inefficient; I don’t know how much data it is). The other, which I would recommend if preloading is not an option, would involve a callback with a cache to save loaded nuclei. Here is that approach:

Element.nuclei = <>; Element.prototype.load_nucleus = function(name, fn) < if ( name in Element.nuclei ) < this.nucleus = Element.nuclei[name]; return fn(); >fs.readFile(name+'.json', function(err, data) < this.nucleus = Element.nuclei[name] = JSON.parse(data); fn(); >); > 

Javascript — Asynchronous constructor, This is how event-based programming works (which is what JavaScript/Node is). You can use option 1, or 2, or any other variation on the …

Источник

Оцените статью