proposal-async-init icon indicating copy to clipboard operation
proposal-async-init copied to clipboard

Suggested problem solution that avoids awaiting in constructors

Open brad4d opened this issue 5 years ago • 5 comments
trafficstars

I believe the goal of this proposal is to be able to write code that looks something like this:

async class Base {
  constructor(fetcher) {
    this.data = await fetcher.getData();
  }

  /...
}

async class Sub extends Base {
  constructor(baseFetcher, subFetcher) {
    await super(baseFetcher);
    this.subData = await subFetcher.getData();
  }

  /...
}

It seems to me that we could get the desired subclassing behavior without having to do await inside the constructor.

Does the code pattern below solve the problem this proposal addresses?

class Base {
  constructor(data) {
    this.data = data;
  }

  static async fetchDataAndCreate(fetcher) {
    return new Base(await fetcher.getData());
  }
  /...
}

class Sub extends Base {
  constructor(baseData, subData) {
    super(baseData);
    this.subData = subData;
  }

  static async fetchDataAndCreate(baseFetcher, subFetcher) {
    return new Sub(await baseFetcher.getData(), await subFetcher.getData());
  }
  /...
}

brad4d avatar Feb 13 '20 22:02 brad4d

Yes, static async methods are the proper solution, no async constructors are needed.

bergus avatar Feb 13 '20 22:02 bergus

I believe the goal of this proposal is to be able to write code that looks something like this [...]

At stage 1 the goal of this proposal is to find out what problems there are with asynchronously initializing instances of classes. It does not tailor to a specific design direction until stage 2. The problems come from class features like class fields/accessors/etc. not having ergonomic integration with asynchronous creation of classes.

Does the code pattern below solve the problem this proposal addresses?

That uses an inversion of control to avoid asynchronous initialization entirely by relying on consumers to perform all asynchronous initialization. This investigation is partly about how to allow code to be written such that asynchronous resources do not require inversion of control. There are a variety of potential workflows we could pursue even if we do end up requiring such an inversion of control. The standardization of asynchronous initialization could be of any reasonable design and if you want to argue for inversion of control, perhaps explaining how to perform ergonomic integration with class fields and accessors such that the class author can ensure encapsulation and allow for asynchronous defaults etc. would help.

bmeck avatar Feb 13 '20 22:02 bmeck

I'm not sure what you mean when you say "ergonomic integration". Can you elaborate? Maybe a code sample showing what you think should work but doesn't with my proposed solution?

Side note: I'm not sure "inversion of control" is what is happening with my solution. https://en.wikipedia.org/wiki/Inversion_of_control

I'd say it's a separation of concerns. Constructors expect to receive actual data to be stored. They are only responsible for storing it correctly in the object being constructed.

brad4d avatar Feb 14 '20 01:02 brad4d

Does the code pattern below solve the problem this proposal addresses?

@brad4d In other topic we discuss same This example too easy: only one function - only one subclass When your structure become complex - all become terrible.

Dimtime avatar Feb 14 '20 06:02 Dimtime

@brad4d

I'm not sure what you mean when you say "ergonomic integration". Can you elaborate?

A lot of JS language progression is in how usable certain workflows are. That idea is the core of lots of features being investigated such as this, previous features such as await, etc. The core here is that various features of JS do not work well with asynchronous initialization.

Constructors expect to receive actual data to be stored. They are only responsible for storing it correctly in the object being constructed.

This is not how JS works today so it doesn't seem really relevant as we cannot change that fact. JS has constructors that perform logic and can contain side effects. The Promise constructor is an example of JS builtin that is explicitly for that purpose of dealing with something with side effects and creating them when the Promise is initialized.

There is truth that this proposal does seek to address issues with workflows people use that do perform logic during construction, but JS the language does explicitly allow logic during construction and that cannot be changed. We should seek to understand the problems people have and serving them.

Authors of JS books even go into detail on how to perform logic and behaviors during initialization so it isn't a strange idea to perform logic, side effects, or async initialization. See https://2ality.com/2019/11/creating-class-instances.html for an example of a prevalent author doing so.

bmeck avatar Feb 17 '20 15:02 bmeck