proposal-async-init
proposal-async-init copied to clipboard
Surely more justification needed, against existing alternative patterns!?
Bluntly, I dislike this whole idea quite intensely.
My reason is as follows:
New features should reduce the chances of bugs. I think this feature increases the surface area for bugs.
Reason: inconsistency between async vs sync classes can lead to mistaken usage in either case!
Leaving the reasons there for now:
On the issue of alternative patterns that already exist, I like to do this:
const
x = new SomethingWithAsyncProcessUponConstruction(),
asyncResult = await x.theAsyncThingImWaitingToGet
;
//
class SomethingWithAsyncProcessUponConstruction {
theAsyncThingImWaitingToGet = this.kickOffTheAsyncProcess();
async function kickOffTheAsyncProcess(){
//
}
}
I think this preserves the strong consistency of classes, making them more dependable, manageable and predictable than if that were broken. Also, I don't find doing this inconvenient whatsoever. Also, in many respects, I find it easier to understand than the proposed idea. So therefore, it is really unclear to me what is being gained by it. But I do see what I regard as being lost by it, and I regard it as significant.
Unfortunately a variety of class features are starting to struggle when integrating against other workflows. This proposal is to seek what problems exist with asynchronous workflows and streamline them such that they are easier and more standardized workflows.
I am open to any approach to make asynchronous initialization better, but this issue does not seem to state any direction to take:
inconsistency between async vs sync classes can lead to mistaken usage in either case!
This needs to be backed up on how this is the case so that an argument for/against the premise can be made. Of note, it seems to have a parallel in async vs non-async functions and clarity in how the problem exists for classes but not for functions would be helpful.
On the issue of alternative patterns that already exist, I like to do this
A variety of workflows do exist for async initialization! However, in particular things like private fields are problematic to work with:
class Query {
#ready;
results;
constructor() {
async function init() {
// setup #ready and await it
}
return init();
}
}
class SpecializedQuery extends Query {
special; // installed on the Promise<Query> not on Query
constructor() {
// cannot access #ready
}
get firstResult() {
// cannot await here (to see if we initialized properly), so must return a Promise
// you could manually manage extra state like a #readyFinished, #readyValue, #readyThrew
// but thats a lot of boilerplate and could be done wrong
}
async foo() {
// need to await #ready in each function
}
}
The proposal is not concerned with the many existing ways to achieve some result; this proposal, like you state, is about the ease of learning/preventing bugs and any missing capabilities. The more complex and/or intricate the workaround to make JS features work together, the more value this proposal seeks to give by standardizing that workflow (whichever it may be).
If the concern is about producing new error prone workflows, I'd happily discuss that; e.g. if it could be shown that the proposal (or one of the possible designs in the FAQ) does new behavior that is problematic or error prone concretely. Doing so would allow discussion and narrowing of what the proposal could need to do to avoid the issue.
The most obvious mistaken usage will be:
const x = new X(); //oops I forgot the await, I now have a Promise, not an X!
If the language approach to mitigate this is to auto-await, then it would be inconsistent with async functions, which I think may lead some programmers (perhaps not seasoned ones) to think that async functions also auto-await! For seasoned programmers, auto-awaiting would still add a cognitive overhead in that if it's a consistent pattern in their code to omit the "await", it would just be something extra to be aware of, if not have to explain to newer developers.
I am open to any approach to make asynchronous initialization better, but this issue does not seem to state any direction to take
I do think the code example in my original comment suffices (I've edited it since to make it shorter).
Let's compare. As per the proposal the same usage code could look something like this:
const
x = await new SomethingWithAsyncProcessUponConstruction(),
asyncResult = x.theAsyncThingImWaitingToGet
;
//
async class SomethingWithAsyncProcessUponConstruction {
theAsyncThingImWaitingToGet = await kickOffTheAsyncProcess();
async function kickOffTheAsyncProcess(){
//
}
}
As you can see, it doesn't appear much (if anything) is being gained. However, I do see the aforementioned disadvantages being introduced.
@TheNavigateur
And what? We have async func now or you forget how work with them too? Async class - just async function constructor
Yes you can do something wrong, but same like with async func already.
Hi @Dimtime !
But what is adding this possibility of doing something wrong supposed to gain us in terms of value as compensation, let alone over-compensation?
The added value of async function was far clearer than async constructor.
As I understand it, the existing example pattern solves the problem space without being any more verbose or inconvenient, in my view.
@TheNavigateur example too simple) Async Class can help work with async objects (files for example)
Right wrapper can be like:
class PromiseClass {
static async new(test = 'test'){
this.promise = test;
return this;
}
constructor(...args) {
let s = async() => PromiseClass.new.call(this, ...args);
return (async r => await s() )();
}//new
}//class
class AsyncClass extends PromiseClass {
static async new(){
return this;
}
constructor(...args){
let s = async() => {
await super();
return AsyncClass.new.call(this,...args);
};
return ( async r => await s() )();
}//new
}//AsyncClass
And with proposal just:
async class PromiseClass {
constructor(test='test'){
this.promise= test;
}
}
async class AsyncClass extends PromiseClass {
constructor(){
await super();
}
}
@Dimtime OK, now you've lost me. A file is not an "async object" as such. It is that the saving and opening of a file's contents can be done asynchronously. Herein perhaps lies the conceptual divergence: asynchronicity is a functional concept, not a data concept.
OK, I have no idea why anybody would do your first example in code; since instance field promises are already inherited normally by subclasses. Even the most basic subclass of the class shown in the example can be used in exactly the same way as shown. I really don't know what I'm missing, from the example you've given.
Even the most basic subclass of the class shown in the example can be used in exactly the same way as shown
@TheNavigateur Can you show me your code for this:
async class Image{
constructor(url){
this.img = await load(url);
}
}
async class EditorImage extends Image {
constructor(url, parm) {
await super(url);
await this.edit(parm);
}
}
async class NewEditorImage extends EditorImage {
constructor(url, parm) {
await super(url, parm);
await this.new_edit(parm);
}
}
let my_image = await new NewEditorImage(url, parm);
class Image{
constructor(parm){
this.imgPromise = this.getImage(parm);
}
async getImage(parm){
await load(parm.url);
}
}
class EditorImage extends Image {
async getImage(parm){
await super.getImage(parm);
await this.edit(parm);
}
}
class NewEditorImage extends EditorImage {
async getImage(parm){
await super.getImage(parm);
await this.new_edit(parm);
}
}
let my_image_data = await new NewEditorImage(parm).imgPromise;
let my_image_data = await new NewEditorImage(parm).imgPromise;
@TheNavigateur Ok i see your way, but seems it was again easy example. Next step i want to add some new func Check and can do it easy:
async class Image{
constructor(url){
this.check = await check(url);
if (this.check) this.img = await load(url);
}
}
async class EditorImage extends Image {
constructor(url, parm) {
await super(url);
await this.edit(parm);
}
}
I dont need change other subclass - just first, can you do same?
@Dimtime Yes really simple. Just add to Image's getImage:
async getImage(parm){
this.check = await check(parm.url);
if (this.check) return await load(parm.url);
}
Yes really simple
@TheNavigateur ok i see
class Async{
constructor(parm){ this.obj = this.getAsyncObj(parm); }
}
class ExtAsync extends Async {
constructor(parm){ this.obj = this.getAsyncObj(parm); }
async getAsyncObj(parm){await super.getAsyncObj(parm);}
}
let my_obj= await new ExtAsync(parm).obj;
For me it is horrible: more words, less standards I must always know how to run new|super And it is just for first look.
Ok we can do wrappers, but i prefer more sugar - not all of this.
@Dimtime
Hi!
For me it is horrible: more words
I think you can clearly see the verbosity is practically the same with existing JavaScript.
less standards
I'm not sure what you mean, but anyway it depends on a person's meaning of the word "standard".
I must always know how to run new|super
Not sure what you mean here. With existing JavaScript, new is new. With the proposal new X() could be an new X or a new promise of an X. This would seem like more cognitive overhead. super is used in both of our examples, so I'm not sure what you're referring to.
Ok we can do wrappers, but i prefer more sugar - not all of this.
More sugar is good, and I'd have no problem with this proposal if I didn't see any disadvantages with it.
I'm not sure what you mean, but anyway it depends on a person's meaning of the word "standard"
@TheNavigateur
- standard better for understanding: if you know pattern then it better than custom wrapper
- for me better less code and less naming
class Async{
constructor(){ this.SomeCustomNameObj = this.getAsyncObj(); }
}
let obj = await new Async().SomeCustomNameObj;
or just
async class Async{
constructor(){ }
}
let obj = await new Async();
More sugar is good
And with sugar maybe we can create more delicious cookies. I just talk about more async init, but all async class may be interesting idea, but need think about details...
if I didn't see any disadvantages with it
I don't see problems... For me it is just new sugar.
I don't see problems
I just wanted to clarify the added problem, in my mind, vs async functions, in response to @bmeck earlier.
When we see the function call getXAsync() we can expect it to technically return anything, naming aside, whereas new XAsync() is invariably a constructed instance of XAsync. It can currently be relied on as a contract with its user.
However, if async classes become a feature, then wherever we see new X() at a JavaScript usage site, we always have to wonder whether it's really an X or a plain old promise of an X. This to me introduces uncertainty and hence a surface are for bugs to be introduced by developers who may not have known better.
Requiring a new keyword like await.new for "async construction" instead of just new would keep the existing constructor contract for new, but it would mean that using new on an async class would open up the asynchronously resolved fields as undefined until they're resolved. This might be acceptable to some people, I don't know.
Otherwise, if you see none of these as "problems", or you think that they are problems but are outweighed by benefits of async classes, then of course you will want them and might even get them. I think I've demonstrated here that verbosity is practically the same with existing JavaScript. So I'm just urging caution.
However, if async classes become a feature, then wherever we see
new X()at a JavaScript usage site, we always have to wonder whether it's really anXor a plain old promise of anX. This to me introduces uncertainty and hence a surface are for bugs to be introduced by developers who may not have known better.
@TheNavigateur Yes, for me it is not big problem, because my wrapper do same, but dirty way... And i want to get some standard way for that...
class PromiseClass {
static async new(test='test'){ this.promise= test; return this;}
constructor(...args) {
let s = async()=>PromiseClass.new.call(this,...args);
return (async r=>await s() )();
}//new
}//class
let obj = await new PromiseClass();
I can go to create module with this class and people can use it, but i hate hell of wrappers and prefer clean standard.
