I have some setup I want during a constructor, but it seems that is not allowed
Which means I can\'t use:
How else should I do this?
<I've found a solution that looks like
export class SomeClass {
private initialization;
// Implement async constructor
constructor() {
this.initialization = this.init();
}
async init() {
await someAsyncCall();
}
async fooMethod() {
await this.initialization();
// ...some other stuff
}
async barMethod() {
await this.initialization();
// ...some other stuff
}
It works because Promises that powers async/await, can be resolved multiple times with the same value.
I had a similar problem in the following case: how to instanciate a 'Foo' class either with an instance of a 'FooSession' class or with a 'fooSessionParams' object, knowing that creating a fooSession from a fooSessionParams object is an async function? I wanted to instanciate either by doing:
let foo = new Foo(fooSession);
or
let foo = await new Foo(fooSessionParams);
and did'nt want a factory because the two usages would have been too different. But as we know, we can not return a promise from a constructor (and the return signature is different). I solved it this way:
class Foo {
private fooSession: FooSession;
constructor(fooSession?: FooSession) {
if (fooSession) {
this.fooSession = fooSession;
}
}
async setup(fooSessionParams: FooSessionParams): Promise<Foo> {
this.fooSession = await getAFooSession(fooSessionParams);
return this;
}
}
The interesting part is where the setup async method returns the instance itself. Then if I have a 'FooSession' instance I can use it this way:
let foo = new Foo(fooSession);
And if I have no 'FooSession' instance I can setup 'foo' in one of these ways:
let foo = await new Foo().setup(fooSessionParams);
(witch is my prefered way because it is close to what I wanted first) or
let foo = new Foo();
await foo.setup(fooSessionParams);
As an alternative I could also add the static method:
static async getASession(fooSessionParams: FooSessionParams): FooSession {
let fooSession: FooSession = await getAFooSession(fooSessionParams);
return fooSession;
}
and instanciate this way:
let foo = new Foo(await Foo.getASession(fooSessionParams));
It is mainly a question of style…
A constructor must return an instance of the class it 'constructs'. Therefore, it's not possible to return Promise<...>
and await for it.
You can:
Make your public setup async
.
Do not call it from the constructor.
Call it whenever you want to 'finalize' object construction.
async function run()
{
let topic;
debug("new TopicsModel");
try
{
topic = new TopicsModel();
await topic.setup();
}
catch (err)
{
debug("err", err);
}
}
Or you can just stick to the true ASYNC model and not overcomplicate the setup. 9 out of 10 times this comes down to asynchronous versus synchronous design. For example I have a React component that needed this very same thing were I was initializing the state variables in a promise callback in the constructor. Turns out that all I needed to do to get around the null data exception was just setup an empty state object then set it in the async callback. For example here's a Firebase read with a returned promise and callback:
this._firebaseService = new FirebaseService();
this.state = {data: [], latestAuthor: '', latestComment: ''};
this._firebaseService.read("/comments")
.then((data) => {
const dataObj = data.val();
const fetchedComments = dataObj.map((e: any) => {
return {author: e.author, text: e.text}
});
this.state = {data: fetchedComments, latestAuthor: '', latestComment: ''};
});
By taking this approach my code maintains it's AJAX behavior without compromising the component with a null exception because the state is setup with defaults (empty object and empty strings) prior to the callback. The user may see an empty list for a second but then it's quickly populated. Better yet would be to apply a spinner while the data loads up. Oftentimes I hear of individuals suggesting overly complicated work arounds as is the case in this post but the original flow should be re-examined.
You may elect to leave the await out of the equation altogether. You can call it from the constructor if you need to. The caveat being that you need to deal with any return values in the setup/initialise function, not in the constructor.
this works for me, using angular 1.6.3.
import { module } from "angular";
import * as R from "ramda";
import cs = require("./checkListService");
export class CheckListController {
static $inject = ["$log", "$location", "ICheckListService"];
checkListId: string;
constructor(
public $log: ng.ILogService,
public $loc: ng.ILocationService,
public checkListService: cs.ICheckListService) {
this.initialise();
}
/**
* initialise the controller component.
*/
async initialise() {
try {
var list = await this.checkListService.loadCheckLists();
this.checkListId = R.head(list).id.toString();
this.$log.info(`set check list id to ${this.checkListId}`);
} catch (error) {
// deal with problems here.
}
}
}
module("app").controller("checkListController", CheckListController)
I know it's quiet old but another option is to have a factory which will create the object and wait for its initialization:
// Declare the class
class A {
// Declare class constructor
constructor() {
// We didn't finish the async job yet
this.initialized = false;
// Simulates async job, it takes 5 seconds to have it done
setTimeout(() => {
this.initialized = true;
}, 5000);
}
// do something usefull here - thats a normal method
usefull() {
// but only if initialization was OK
if (this.initialized) {
console.log("I am doing something usefull here")
// otherwise throw error which will be catched by the promise catch
} else {
throw new Error("I am not initialized!");
}
}
}
// factory for common, extensible class - thats the reason of the constructor parameter
// it can be more sophisticated and accept also params for constructor and pass them there
// also, the timeout is just example, it will wait about 10s (1000 x 10ms iterations
function factory(construct) {
// create a promise
var aPromise = new Promise(
function(resolve, reject) {
// construct the object here
var a = new construct();
// setup simple timeout
var timeout = 1000;
// called in 10ms intervals to check if the object is initialized
function waiter() {
if (a.initialized) {
// if initialized, resolve the promise
resolve(a);
} else {
// check for timeout - do another iteration after 10ms or throw exception
if (timeout > 0) {
timeout--;
setTimeout(waiter, 10);
} else {
throw new Error("Timeout!");
}
}
}
// call the waiter, it will return almost immediately
waiter();
}
);
// return promise of object being created and initialized
return aPromise;
}
// this is some async function to create object of A class and do something with it
async function createObjectAndDoSomethingUsefull() {
// try/catch to capture exceptions during async execution
try {
// create object and wait until its initialized (promise resolved)
var a = await factory(A);
// then do something usefull
a.usefull();
} catch(e) {
// if class instantiation failed from whatever reason, timeout occured or usefull was called before the object finished its initialization
console.error(e);
}
}
// now, perform the action we want
createObjectAndDoSomethingUsefull();
// spagetti code is done here, but async probably still runs