09:25
<voidhedron>
Why is it that async constructors are not valid syntax sugar, even though you can manually implement them without any issues?
09:26
<voidhedron>

Why was it decided that we should have to write:

class Foo {
    constructor() {
        return new Promise(async (resolve, reject) => {
            this.sync = 123;
            this.async = await someAsyncCall();
            resolve(this);
        });
    }
}

instead of just

class Foo {
    async constructor() {
        this.sync = 123;
        this.async = await someAsyncCall();
    }
}
09:27
<voidhedron>
await new Foo() with the 1st codeblock works perfectly fine
09:28
<voidhedron>
even the syntax itself is already in place for other methods of classes, async methodName() { ... }
09:28
<voidhedron>
why was it decided to go out of our way to specifically block the syntax when methodName === constructor
09:30
<nicolo-ribaudo>
It's not without issues -- it does not work with subclasses
09:36
<voidhedron>

works for me, just needs to be a little different from sync subclassing, which a dedicated syntax sugar could do

class Bar extends Foo {
    constructor() {
        return new Promise(async (resolve, reject) => {
            const base = await super();
            base.barSync = 123;
            base.barAsync = await someAsyncCall();
            resolve(base);
        });
    }
}
09:36
<nicolo-ribaudo>
But now you cannot define private fields/methods in Bar
09:37
<nicolo-ribaudo>
A new "async constructor" syntax would solve this, but it's not just syntax sugar on top of what we can already do
09:39
<voidhedron>
hmm I see, you're right on those
09:54
<voidhedron>
so the problem seems to be super() attaches the caller's private properties on its own return value before I get the chance to await the Promise, so I get a promise with private methods... rather cursed to see
09:55
<voidhedron>
is there a reason for this behavior of applying privates at a different step than normal properties?
10:03
<voidhedron>
huh... this leads to some interesting things being possible...
10:09
<nicolo-ribaudo>

It's the same for normal properties:

class Bar extends Foo {
    prop = 2;
    constructor() {
        return new Promise(async (resolve, reject) => {
            const p = super();
            console.log(p.prop); // 2
            const base = await p;
            resolve(base);
        });
    }
}
10:12
<voidhedron>
well this is weird, since when does Chrome console allow you to directly access private members of classes externally
10:16
<nicolo-ribaudo>
Oh nice, I didn't know about that
10:16
<nicolo-ribaudo>
That's useful
10:16
<voidhedron>

It's the same for normal properties:

class Bar extends Foo {
    prop = 2;
    constructor() {
        return new Promise(async (resolve, reject) => {
            const p = super();
            console.log(p.prop); // 2
            const base = await p;
            resolve(base);
        });
    }
}
Oh I see, my "normal" property was actually a getter, and those end up in the promise result for some reason
10:16
<voidhedron>
like get prop() { return 2 };
10:17
<nicolo-ribaudo>
Because it's on the prototype, so it's installed long before creating this
10:18
<voidhedron>
oh right that's why it shows shadowed... I don't use Chrome console much I usually test this stuff on Node REPL so I got confused there (like with the .#prop thing above) 😅
10:19
<voidhedron>
on Node it just wouldn't show on the same level if its on prototype
10:21
<voidhedron>
but in any case, back to the interesting implication I realized...
10:21
<voidhedron>
We aren't normally allowed to have private properties on plain objects, and syntax like let obj = { #foo: 123 } is not allowed, but...
10:22
<voidhedron>
...this works
10:23
<voidhedron>
We aren't normally allowed to have private properties on plain objects, and syntax like let obj = { #foo: 123 } is not allowed, but...
so at this point my next obvious question has to be, why isn't plain object privates syntax like here allowed? It's clearly not for technical restrictions of JS engines at least...
10:29
<voidhedron>
but wait... it gets deeper, you can inject private properties into existing objects with this...
10:30
<voidhedron>
not at all production safe, hell this whole concept is cursed off the tracks, but with some eval tricks you can even make it fully dynamic...
15:27
<ljharb>
voidhedron: if you return a promise from the constructor, then new AsyncThing() instanceof AsyncThing will be false
15:44
<voidhedron>
seems logical enough to me? It will work if you await the left side
15:45
<voidhedron>
thats like saying typeof asyncFunction() will always be object even if the function returns something else... of course, you didn't await it...
15:47
<voidhedron>
I do agree that having a non-awaited lingering promise result from a constructor is weird though, but I just see that as another reason to have the async constructor syntax sugar, it could be made so that when the syntax sugar is used, the constructor must be await'ed and not awaiting it would be an error
15:49
<voidhedron>
async constructor would be statically analyzable to engines should be able to tell at compile time if new X is an async constructor or not, an error or not due to lack of preceding await
16:32
<bakkot>
so at this point my next obvious question has to be, why isn't plain object privates syntax like here allowed? It's clearly not for technical restrictions of JS engines at least...
because no one has done the work to write out all the details of how it should work and convince the committee that it should happen
16:32
<bakkot>
this is also the answer to the async constructor question
16:32
<bakkot>
the work has started but not finished in both cases
16:33
<bakkot>
see respectively https://github.com/tc39/proposal-private-declarations and https://docs.google.com/presentation/d/1DsjZAzBjn2gCrr4l0uZzCymPIWZTKM8KzcnMBF31HAg/edit
16:34
<bakkot>
async constructor would be statically analyzable to engines should be able to tell at compile time if new X is an async constructor or not, an error or not due to lack of preceding await
but, this is false: X can be dynamically rebound and is therefore not at all statically analyzable, in general
16:34
<bakkot>
something like TypeScript can do that analysis but actual engines generally would not
16:39
<Ashley Claymore>

TIL:

new class {
  "constructor"() { console.log("hi") } // logged
}
new class {
  ["constructor"]() { console.log("hi") } // not logged
}

Similar to["__proto__"] vs "__proto__"

19:59
<voidhedron>
see respectively https://github.com/tc39/proposal-private-declarations and https://docs.google.com/presentation/d/1DsjZAzBjn2gCrr4l0uZzCymPIWZTKM8KzcnMBF31HAg/edit
oh I see, issue #12 of the private decl's one even specifically talks about all the plain object private tricks I brought up, wonderful information thank you
20:00
<voidhedron>
I will keep an eye on those two proposals from now on I suppose
20:01
<voidhedron>

TIL:

new class {
  "constructor"() { console.log("hi") } // logged
}
new class {
  ["constructor"]() { console.log("hi") } // not logged
}

Similar to["__proto__"] vs "__proto__"

what is the technical explanation behind this stuff?
20:03
<bakkot>
it should be possible to identify when you are defining a prototype/constructor purely syntatically
20:03
<bakkot>
if you could use computed keys to do those, that would not be possible
20:04
<bakkot>
e.g. let key = foo(); return { [key]: null } - does that specify a prototype or not? the answer is that it does not, no matter what key is, because only static __proto__ keys are special, not dynamic ones
20:43
<Ashley Claymore>
and in terms of spec the syntax-directed-operation for handing the constructor of a class body (https://tc39.es/ecma262/#sec-static-semantics-classelementkind) uses https://tc39.es/ecma262/#sec-static-semantics-propname which returns 'empty' for all computedPropertyNames
22:12
<bakkot>
I am updating the base64 proposal to support decoding into an existing buffer; would appreciate any feedback https://github.com/tc39/proposal-arraybuffer-base64/pull/26