2023-01-02 [18:26:55.0326] > <@ljharb:matrix.org> Promise has the brand checks but no non-side-effect way to check it; and error sadly has no way to do the brand check, yes. Yet. We did discuss this on matrix a [few months ago](https://matrixlogs.bakkot.com/TC39_General/2022-05-11#L9). The inability to brand check a promise without side effects (in particular triggering proxy traps) is a major pain for us, and I would very much would like to find a way to make this possible. But a lot of people expressed concerns with revealing such a power, mostly about users doing conditional work based on the result type (promise or not, aka releasing Zalgo) [18:34:42.0565] Yes, and my recommendation is that our stance should be to enable and help people to avoid releasing Zalgo, not prevent people from releasing Zalgo. The latter is not possible. The former is possible while still revealing a brand check. It’s not the same magnitude of hazard as, say, an API to synchronously query the state of a promise. [20:31:57.0810] Since we're talking about this stuff, reminder that https://github.com/tc39/proposal-faster-promise-adoption exists [08:13:44.0011] Hello everyone. I've been trying to understand https://tc39.es/ecma262/#sec-functiondeclarationinstantiation. Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? [08:14:19.0416] * Hello everyone. I've been trying to understand https://tc39.es/ecma262/#sec-functiondeclarationinstantiation. Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? [08:19:34.0557] * Hello everyone. I've been trying to understand https://tc39.es/ecma262/#sec-functiondeclarationinstantiation. Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? I'm excluding the VE because there's only 1 reference (20d) and it looks only related to either a strict function or a function with Parameter Expressions. [08:20:08.0106] * Hello everyone. I've been trying to understand https://tc39.es/ecma262/#sec-functiondeclarationinstantiation. Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? I'm excluding the VE because there's only 1 reference (20d) and it looks only related to either a strict function or a function with Parameter Expressions, but this function has no parameters at all. [08:31:00.0977] * Hello everyone. I've been trying to understand [https://tc39.es/ecma262/#sec-functiondeclarationinstantiation](). Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? I'm excluding the VE because there's only 1 reference (20d) and it looks only related to either a strict function or a function with Parameter Expressions, but this function has no parameters at all. This is closely related to [a StackOverflow question](https://stackoverflow.com/questions/70279115/does-lexicalenvironment-s-outerenv-refer-to-the-variableenvironmentof-same) which unfortunately didn't get the answer needed. [08:34:34.0704] * Hello everyone. I've been trying to understand [https://tc39.es/ecma262/#sec-functiondeclarationinstantiation](https://app.element.io/). Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? I'm excluding the VE because there's only 1 reference (20d) and it looks only related to either a strict function or a function with Parameter Expressions, but this function has no parameters at all. This is closely related to [a StackOverflow question](https://stackoverflow.com/questions/70279115/does-lexicalenvironment-s-outerenv-refer-to-the-variableenvironmentof-same) ([this comment](https://stackoverflow.com/questions/70279115/does-lexicalenvironment-s-outerenv-refer-to-the-variableenvironmentof-same#comment124269800_70297307) in particular) which unfortunately didn't get the answer needed. [08:54:34.0937] * Hello everyone. I've been trying to understand [https://tc39.es/ecma262/#sec-functiondeclarationinstantiation](https://app.element.io/) as applied to functions without parameters. Say I have the function: ```JavaScript function f() { const x = 2; let y = 3; var z = 4; } ``` Is a single Lexical Environment created for x,y,z or is z stored on the first LE and x,y on another one with Outer Environment set to the first LE...or something else I didn't think of? I'm excluding the VE because there's only 1 reference (20d) and it looks only related to either a strict function or a function with Parameter Expressions, but this function has no parameters at all. This is closely related to [a StackOverflow question](https://stackoverflow.com/questions/70279115/does-lexicalenvironment-s-outerenv-refer-to-the-variableenvironmentof-same) ([this comment](https://stackoverflow.com/questions/70279115/does-lexicalenvironment-s-outerenv-refer-to-the-variableenvironmentof-same#comment124269800_70297307) in particular) which unfortunately didn't get the answer needed. [09:10:32.0616] voide: it depends on whether the function is in a strict context or not. in a non-strict context, "z stored on the first LE and x,y on another one with Outer Environment set to the first LE" is accurate - see step 30/31. [10:57:28.0733] Thanks for confirming. Does this mean that: > Set the LexicalEnvironment of calleeContext \[...\] (step 32) refers to a new LE, _not_ the first one? I feel like this is the missing puzzle piece for me. [10:58:00.0224] * Thanks for confirming. Does this mean that: > Set the LexicalEnvironment of calleeContext \[...\] (step 32) refers to a new LE, _not_ the first one? I feel like this is the missing puzzle piece for me. [11:01:08.0497] I suppose it's the _the_ article that throws me off. [11:01:31.0109] * I suppose it's the _the_ article that throws me off. [11:01:41.0616] Not sure if this'll help but: in spec terms, there isn't a thing that *is* a Lexical Environment. Rather, `LexicalEnvironment` is simply the name of a component of an execution context. [11:02:47.0975] So the `Set` step is setting a component of `calleeContext`. It's like `calleeContext` is a Record, and `LexicalEnvironment` is simply the name of one of its fields. [11:04:59.0243] Yes, this is something I understand now and I know we're talking about abstractions. Thing is, by the time that step is being executed there's already a Lexical Environment with a **var** declaration and that part confuses me a little. [11:05:00.0128] The value of that component/field is an Environment Record. [11:06:19.0739] This: > So the Set step is setting a component of calleeContext Is language that is clear to me because you said it's _a_ component instead of _the_ component. [11:06:25.0552] * This: > So the Set step is setting a component of calleeContext Is language that is clear to me because you said it's _a_ component instead of _the_ component. [11:07:28.0209] If I had a record `_foo_` with a field `[[Bar]]`, I could say `Set the [[Bar]] field of _foo_ to 0.` [11:08:33.0109] It wouldn't be correct to say `Set a [[Bar]] field of _foo_ to 0.` [11:11:13.0461] voide: you might find it useful to look at https://github.com/engine262/engine262/blob/3248ccc6793a4de3ca6cab1d3a16a113ddc8d0c9/src/runtime-semantics/FunctionDeclarationInstantiation.mjs#L237, which is a JS implementation of this algorithm [11:12:14.0579] > <@jmdyck:matrix.org> If I had a record `_foo_` with a field `[[Bar]]`, I could say `Set the [[Bar]] field of _foo_ to 0.` Yes, I understand what you say. [11:12:32.0715] > <@bakkot:matrix.org> voide: you might find it useful to look at https://github.com/engine262/engine262/blob/3248ccc6793a4de3ca6cab1d3a16a113ddc8d0c9/src/runtime-semantics/FunctionDeclarationInstantiation.mjs#L237, which is a JS implementation of this algorithm This is appreciated, I will take a look at it. 2023-01-03 [23:53:40.0771] yulia | sick: littledan: I could make the Module Loading call tonight if that's helpful, please lmk [06:48:24.0470] A lot of people seem to be out today; I think we should try to convene in two weeks for the module call [08:02:33.0426] > <@bakkot:matrix.org> Since we're talking about this stuff, reminder that https://github.com/tc39/proposal-faster-promise-adoption exists Yes I need to get back to that proposal. I have on my list of tasks to rewrite a user land promise implementation that side-steps some other issues we've found with promises (e.g. memory leaks), and has the ability to get insight into the adoption of unresolved promises. I'm hoping that could help me find ways to rewrite the promise steps in a way that is both mostly compatible with existing behavior, but also enshrines some beneficial behavior that is currently left out by most implementations, such as detection of cycles longer than 2 promises, or these extra ticks incurred by adoption. [13:10:17.0300] I think we could pretty easily expose the is-not-a-proxy-and-doesn't-have-an-own-`.then` with that proposal (but we'd be getting further away from faster promise adoption core) [13:10:36.0800] At least, we could refactor the check in a way that it can be exposed later. 2023-01-09 [10:12:46.0639] does anyone have a proposal that they want HCI research done on? [11:42:56.0111] heads up that test262's [async helpers RFC](https://github.com/tc39/test262/pull/3724) has moved to final comment period until 2023-01-19. this is the first RFC according to our new RFC process so additionally if you have comments on the process they are welcome on the [draft process document](https://github.com/tc39/test262/pull/3525) 2023-01-11 [18:47:19.0201] I wonder about the use rate of new css features like new units, new color functions [18:49:18.0983] Because css cannot be polyfilled [21:14:19.0973] I would guess that the use rate of new css features would go up as autoprefixer/other css preprocessors add support for them, which is basically polyfilling [22:56:40.0518] Many features cannot be polyfilled because they don't have an old equivalent version, for example different color functions do not represent the same color space. Units are the same [05:19:18.0939] https://preset-env.cssdb.org/ atleast claims to get the job done 2023-01-12 [08:53:37.0858] practitioners: do you expect/want `Error.prototype.stack` strings to chain together stacks from `.cause`? or do you only expect that chaining to happen in more "sophisticated" printing places like the DevTools console and console.log? [08:54:17.0680] i feel like it's weird to have the `.stack` string automatically chase `.cause` chains because then you have to parse the string to figure out which is a particular error object's stack [08:55:26.0073] 🤔 That's an interesting problem. I believe both have their use cases. Is it possible to make this feature configurable in the devtools? [08:55:58.0113] i guess that's possible, but it doesn't feel right to have devtools configure the fundamental behavior of the `.stack` string [08:56:50.0737] for context: Firefox only chase `.cause` stacks in the more sophisticated printing places like console, afaict [08:56:57.0344] I’d expect DevTools to help introspect both cause and aggregate error, and I expect neither to be reflected in the error.stack. [08:57:08.0507] that's my intuition, Kris Kowal [08:57:24.0807] trying to decide what to do for chrome, that choice (which FF already made) seems like the right one [08:57:25.0885] Yeah, firefox's behavior is good [08:58:16.0116] Developers don't type err.stack (in the console), they type err [08:58:27.0079] Meanwhile, inspecting a causal graph is close to what Miller Columns were invented for. [08:58:32.0246] i see [08:58:38.0085] And that should track the causes so it shouldn't be reflected in the stack string [08:58:45.0240] programmatically it's pretty easy to just keep chasing `.cause` yourself anyway [08:58:50.0771] if you want to build up the stack string with all the cause chain [09:00:21.0266] Domenic certainly will have opinions. He implemented Q’s “long traces”. [09:23:46.0855] > <@shuyuguo:matrix.org> practitioners: do you expect/want `Error.prototype.stack` strings to chain together stacks from `.cause`? or do you only expect that chaining to happen in more "sophisticated" printing places like the DevTools console and console.log? i expect devtools to chase it [09:24:15.0251] each individual error's stack string should ideally remain as-is [10:18:04.0911] I also agree that's the right behavior [10:35:17.0563] Can we have a second getter called `fullStack`? 😂 [10:42:59.0206] `.tellMeWhy` 2023-01-13 [18:01:09.0843] > <@shuyuguo:matrix.org> `.tellMeWhy` returns `{ aintNothinButA: SyntaxError }` [18:27:56.0782] ensures `assert.doesNotMatch(e.message, /ThatWay/)` [18:31:54.0184] * ensures `assert.doesNotMatch(e.message, /ThatWay/)` [01:29:44.0385] Has `collection.isEmpty()` ever been suggested? [01:49:40.0119] For `Array` too? To gloss over `length ` vs `size`? [01:57:38.0511] Dunno, just curious if it came up in general for any kind of collection I suppose. There's some demand for it for `URLSearchParams`. [10:28:47.0170] `x.isEmpty()` vs `x.size === 0` doesn't seem hugely valuable [10:29:50.0423] > <@annevk:matrix.org> Dunno, just curious if it came up in general for any kind of collection I suppose. There's some demand for it for `URLSearchParams`. Do you have a reference on-hand to this demand? [10:30:13.0167] (I guess I'm also a little surprised; I haven't seen this demand raised in a general JS context) 2023-01-14 [22:55:14.0940] littledan: https://github.com/whatwg/url/issues/163 (demand might be a strong word, it came up and seemed like a reasonable request) [22:56:43.0096] But yeah, maybe as TabAtkins argued there we should just do `size`, part of the hesitancy has been JS not having a multimap [15:13:56.0520] i'm cool with an is empty [15:14:34.0994] all the various things in rust have is_empty and its nice 2023-01-15 [23:14:06.0443] Yeah, Infra "is empty" is relatively common in specs too [06:45:34.0506] yeah, I'd be fine with something like that, but locally adding size seems right 2023-01-19 [04:35:15.0073] I'm looking into https://github.com/whatwg/streams/pull/1083, which allows creating a `ReadableStream` from a (sync or async) iterable [04:35:20.0289] This PR wants to call `AsyncIteratorClose`, but that AO uses `Await()` [04:35:34.0319] * This PR wants to call `AsyncIteratorClose`, but that AO uses `Await()` [04:36:45.0884] and given that `Await()` works by suspending and replacing execution contexts, it's not clear whether that is something a non-TC39 spec can use [05:37:57.0335] I think there isn’t a binary switch that gets flipped between these layers, and that it’s OK to do some TC39-style at the boundaries as long as it doesn’t get too confusing. But this is really a question for WHATWG folks. [09:02:55.0102] Andreu Botella: we will probably add proper support for built-in async functions soon, which will provide machinery for defining a promise-returning function where the body of the function can use `Await` without disruption - https://github.com/tc39/ecma262/pull/2942 [09:05:16.0712] prior to that PR, you're right that things are not really set up for it. my suggestion in the mean time would be, you don't actually need to use `AsyncIteratorClose` directly; if you trace through all the machinery, all it's really doing is calling the `return` method and then calling `.then` on the result with a continuation for remainder of the current function, which you can probably figure out how to do manually 2023-01-20 [00:18:27.0418] littledan: I paged most of the scoping discussion we had out again, but I was wondering if https://github.com/whatwg/html/issues/8476 and https://github.com/fserb/canvas2D/blob/master/spec/layers.md are something it could help with. In particular giving you syntax for exiting the scope that would then result in `endLayer()` getting invoked. (As well as allow for nesting of scopes.) [04:37:41.0496] Oh is this a potential place where the `using` declaration could help? [04:41:09.0214] Yeah I think so, although not sure about the nesting thing [04:45:13.0192] Annevk: it would be great to have your thoughts in https://github.com/w3ctag/design-reviews/discussions/800 especially if it might feed back into any needed changes for the disposal API—it’d be ideal to work all that out before the whole set of proposals reaches Stage 3 [08:27:06.0831] I essentially put my earlier comment from here, there. Hope that's useful :-) [09:43:57.0743] Hi, I asked where I could post my objection to the pipe operator `|>`, and Chris de Almeida kindly pointed me here. So here is my objection, copy/pasted from Github: > JavaScript's syntax is already sufficiently complicated enough. > > Have you people seen what happened to C -> C++? Is that your goal too? [09:45:13.0009] Greg: if it wasn't clear from my github comment, the tone of your objection is not in the spirit of our code of conduct [09:45:47.0284] is there something specific about the pipeline operator you think should be different? a generic "new things are complicated" isn't really productive [09:46:34.0685] I think the pipe operator shouldn't exist, period. That is my objection. My reasoning is the disaster that is C++. Anyone familiar with C++ knows what I'm talking about, I think. [09:47:20.0994] When a language adds more and more syntax, it makes the language more difficult to use [09:47:32.0216] that objection is basically useless, because lots of us think it should [09:47:34.0051] This additional syntax represents a cognitive burden to the developer [09:47:36.0272] and i'm not sure how C++ is relevant [09:47:43.0178] yes, all additional features represent a cognitive burden [09:47:46.0129] should we all pack up and go home? [09:47:56.0149] that's not a useful argument [09:48:02.0153] No, we should design good languages. I have a more longform blog post on this subject here: https://www.taoeffect.com/blog/2010/01/how-newlisp-took-my-breath-and-syntax-away/ [09:48:22.0513] "good" is subjective, and "less syntax" is not an objectively good quality (nor is "more syntax" an objectively good one, ofc) [09:48:23.0196] `|>` is unnecessary complexity [09:48:29.0592] unnecessary *for you*, perhaps [09:48:33.0164] clearly necessary for some [09:48:37.0502] It's literally unnecessary [09:48:52.0537] How is it necessary? The proposal shows how things can be written using the existing syntax in a shorter way [09:48:54.0311] it's necessary if you want to express a sequence of statements in a left-to-right order [09:49:05.0440] "shorter" isn't the important bit here - "left to right" is. [09:49:23.0884] clarity, not brevity, is what's important when writing code. [09:49:41.0208] Then my proposal would be to change JavaScript so that this symbol can be used in a way that does not add new syntax [09:49:42.0945] sometimes those are the same, sometimes they're not [09:49:58.0108] it's not possible to do without new syntax. [09:49:58.0983] For example, in LISPs the `|>` operator is trivial to add without adding new syntax to the language [09:50:06.0009] yes but JS isn't and never can be a LISP [09:50:15.0193] for example, you can't `await` in the middle of a pipeline unless it's syntax. [09:50:48.0934] i hear you that you're upset that JS doesn't work the same as a language you like (LISP, i assume?), but that's not the same thing as "it's possible to make JS into the language you like" [09:51:32.0809] btw i hope you realize that i'm truly trying to have a good-faith discussion with you here, despite your abrupt and confrontational entrance. you're not being ignored or brushed aside. [09:53:09.0238] I appreciate that, and I realize that is exactly how I entered this discussion, and that it would probably have been better to do so with more diplomacy. I am however time pressured at the moment, and it was either that abrupt reply or saying nothing at all [09:53:58.0582] Please consider the road this is putting JS down. How much more syntax can the language handle before it becomes a monstrosity to more people? It is already a monstrosity to many [09:54:26.0615] there are a number of folks on TC39 who share your general sentiment that "too much syntax" is something to avoid. the "tragedy of the common lisp" article has been cited dozens of times [09:54:42.0447] that's not an argument against any specific proposal though, and it doesn't have consensus as a ban on further syntax. [09:55:07.0842] so rest assured, if that's the extent of your pushback on this proposal, your viewpoint has long been strongly represented. [09:55:32.0445] OK, thanks for hearing me out [09:55:36.0844] absolutely [09:55:58.0375] nobody wants JS to become a syntax soup that's hard for people to learn and understand - even those that are enthusiastic about specific syntax proposals [09:57:46.0896] Greg: also, FWIW, know that preventing unnecessary bloat in the language is an issue that is regularly raised in plenary, and proposals absolutely must pass that sniff test to go anywhere [10:08:09.0210] I guess I would ask the people who are working on modifying JavaScript: when is enough enough (in terms of the syntax)? Does such a point exist? Or will the syntax be modified into perpetuity? There are costs to this: 1. Cognitive burden to the developer. 2. Cost burden to companies to have to continually spend resources on upgrading their tooling. 3. Costs to browser vendors and people who are interested in building new operating systems that can browse the web - from scratch. Have a look at SerenityOS. Please consider Andreas Kling. Think of what you're doing to him and others like him. So again. Is there a line that shouldn't be crossed, and what does that line look like? It would be nice if that question were decided upon, at some point. [10:09:02.0751] https://erights.medium.com/the-tragedy-of-the-common-lisp-why-large-languages-explode-4e83096239b9 [10:09:09.0323] Maybe you're interested in this article [10:13:41.0678] Thanks Jack Works, yes it's saying essentially the same thing, nice article [10:16:07.0783] * I guess I would ask the people who are working on modifying JavaScript: when is enough enough (in terms of the syntax)? Does such a point exist? Or will the syntax be modified into perpetuity? There are costs to this: 1. Cognitive burden to the developer. 2. Cost burden to companies to have to continually spend resources on upgrading their tooling. 3. Costs to browser vendors and people who are interested in building new operating systems that can browse the web - from scratch. Have a look at SerenityOS. Please consider Andreas Kling. Think of what you're doing to him and others like him. 4. The percentage of the web that will become unbrowesable to various devices. So again. Is there a line that shouldn't be crossed, and what does that line look like? It would be nice if that question were decided upon, at some point. [10:16:33.0811] * I guess I would ask the people who are working on modifying JavaScript: when is enough enough (in terms of the syntax)? Does such a point exist? Or will the syntax be modified into perpetuity? There are costs to this: 1. Cognitive burden to the developer. 2. Cost burden to companies to have to continually spend resources on upgrading their tooling. 3. Costs to browser vendors and people who are interested in building new operating systems that can browse the web - from scratch. Have a look at SerenityOS. Please consider Andreas Kling. Think of what you're doing to him and others like him. 4. The percentage of the web that will become unbrowesable to various devices. 5. The increased centralization of browsers themselves. So again. Is there a line that shouldn't be crossed, and what does that line look like? It would be nice if that question were decided upon, at some point. [10:16:45.0541] Edited my comment above to add 2 more costs: > 4. The percentage of the web that will become unbrowesable to various devices. > 5. The increased centralization of browsers themselves. [10:22:18.0808] * I guess I would ask the people who are working on modifying JavaScript: when is enough enough (in terms of the syntax)? Does such a point exist? Or will the syntax be modified into perpetuity? There are costs to this: 1. Cognitive burden to the developer. 2. Cost burden to companies to have to continually spend resources on upgrading their tooling. 3. Costs to browser vendors and people who are interested in building new operating systems that can browse the web - from scratch. Have a look at SerenityOS. Please consider Andreas Kling. Think of what you're doing to him and others like him. 4. The percentage of the web that will become unbrowseable to various devices. 5. The increased centralization of browsers themselves. So again. Is there a line that shouldn't be crossed, and what does that line look like? It would be nice if that question were decided upon, at some point. [10:25:04.0906] Little of that seems specific to syntax [10:35:07.0720] Andreu Botella: how is it not specific to syntax? [10:35:25.0907] * Andreu Botella: how is it not specific to syntax? [10:37:30.0908] Any change in the language will mean changes for users, tooling and engines [10:40:04.0055] The standard of the language as it impacts browser developers is primarily the syntax, but sure, there might be other aspects of the language that could have these costs too [10:54:43.0324] I don't think that's true [10:55:13.0950] JS has a bunch of built-ins that are defined by the spec [10:56:25.0391] I am agreeing with you, so I'm not sure what you're saying isn't true, if you could be more specific it'd be appreciated [11:10:41.0499] Greg: I see the basis of your argument but I think to some extent you're overestimating the effect of syntax additions of the language. [11:20:29.0539] ryzokuken: Has the number of independent browsers supporting the latest features of JavaScript gone up or down over time? [11:22:02.0584] Irrespective of the answer, the point I'm trying to make is that syntax additions to JavaScript are far from the main cause of these things. [11:27:11.0094] I think a much better argument can be made on the grounds of ergonomics and language complexity [11:27:20.0643] But even there, things aren't so black and white IMO [11:28:00.0604] Syntax additions to the language can actually make it more straightforward to code in many cases. [11:35:55.0686] generally in all cases, i'd hope [15:00:44.0840] > <@taoeffect:matrix.org> ryzokuken: Has the number of independent browsers supporting the latest features of JavaScript gone up or down over time?  Also worth noting that while this may be true of _browsers_, it's not necessarily true of _runtimes_ more broadly. With Deno + Bun becoming more popular, plus more niche projects like Jerryscript, Clouflare Workers, edge, etc., it actually seems like the number of runtimes is _increasing_, despite the expanding complexity of the language. This is really an argument for adding nothing new to the web platform as a whole. 2023-01-21 [18:46:03.0291] James DiGioia (mAAdhaTTah on GH): AFAIK neither Deno nor Bun use new runtimes but piggy back off of existing ones [19:06:24.0304] For what it’s worth, XS is maintained by approximately one and a half people and follows spec proposals. [19:25:11.0953] serenity's libjs too [11:04:15.0854] I really don’t think embeddings of V8 or JSC should “count” as extra implementations, the way XS and LibJS do. However, they have significant ecosystem impact and deserve to be considered in that way. [11:04:40.0810] So, when we talk about independent implementations: yes, that has been growing! It is great 2023-01-24 [19:41:57.0070] I remember seeing something years ago, but I can't remember if it had a name. A keyword on functions that reversed the async/await calls, so every result in the function was awaited unless marked as async. Anyone know what I'm talking about? [19:42:28.0769] * I remember seeing something years ago, but I can't remember if it had a name. A keyword on functions that reversed the async/await calls, so every result in the function was awaited unless marked as async. Anyone know what I'm talking about? [19:43:07.0696] i'm not sure how that could reasonably work. it might have been on esdiscuss or the discourse tho [21:13:04.0636] https://github.com/ziolko/babel-plugin-auto-await ? [21:13:50.0882] ah yes, that was probably it. Thank you. [21:20:29.0283] It's not the discussion I was thinking of, but I think it might have been referenced. Like a lot of modern projects I deal with a lot of async code. Was thinking if there wasn't a quick fix for simple functions. (Assuming the programmer knows what they're doing awaiting everything in a function). [21:20:43.0208] > <@aclaymore:matrix.org> https://github.com/ziolko/babel-plugin-auto-await ? Looks like a performance super footgun (if no type information involved)🤣 [21:21:06.0330] * Looks like a performance super footgun (if no type information involved)🤣 [21:21:09.0672] yes, it would be, especially on large functions. [12:24:43.0658] re: pipeline functionality, while I frequently complain about the increasing complexity of JS (and feel that some of the more recent additions were a mistake), I do not agree that the pipeline syntax is pointless or unnecessary. it solves a very real, irritating and widespread problem (mixing method and function invocations in a readable manner), and as someone who actually *is* maintaining a [userland implementation](https://www.npmjs.com/package/syncpipe) of sorts for this kind of functionality, the inability to use `await` is a limitation I haven't been able to find a workaround for without language changes either. using regular old Promise syntax (ie. `.then`) is a little better, but only a little. [12:25:33.0234] I really hate new things being added to core in general but this is one of the few things that passes even my criteria :p [12:48:54.0943] not totally convinced by "method and function invocations in a readable manner" - you can go one way (methods, then functions), but not the other (functions, then methods) [12:51:04.0619] like: ``` x .filter() .transform() |> apply1(%) |> apply2(%) ``` works fine, but if the function applications are first, you're stuck with ``` (x |> apply1(%) |> apply2(%)) .filter() .transform() ``` or giving up on using the original method application syntax entirely, as in ``` x |> apply1(%) |> apply2(%)) |> %.filter() |> %.transform() ``` [13:51:09.0268] ``` x |> apply1(%) . filter(f) |> apply2(%) . map(f2) ``` Brackets aren't required, if ok that the scope of the pipe leaks into subsequent lines. [13:51:31.0570] Though I would probably write it like the last example with all pipes [13:52:46.0787] > <@bakkot:matrix.org> like: > ``` > x > .filter() > .transform() > |> apply1(%) > |> apply2(%) > ``` > works fine, but if the function applications are first, you're stuck with > ``` > (x > |> apply1(%) > |> apply2(%)) > .filter() > .transform() > ``` > or giving up on using the original method application syntax entirely, as in > ``` > x > |> apply1(%) > |> apply2(%)) > |> %.filter() > |> %.transform() > ``` * Though I would probably write it like the last example with all pipes [15:07:46.0680] In https://tc39.es/ecma262/#sec-returnifabrupt, what's the purpose of this step: > Else, set hygienicTemp to hygienicTemp.[[Value]]. [15:11:10.0145] See https://github.com/tc39/ecma262/pull/1570 [15:28:18.0094] jmdyck: Thanks! 2023-01-25 [18:39:03.0599] Was thinking more about async stuff. https://pastebin.com/mtMypZC9 I've never needed async getter/setters, but introducing a ~= operator could make the setter part feasible for equality and destructuring potentially. [14:37:31.0093] bakkot: Is there an issue or chat for parallel async iterators? [14:44:46.0637] We've had similar discussion before: https://matrixlogs.bakkot.com/TC39_General/2022-04-19#L47-L71 [14:45:37.0878] I guess it'd be ok if the iterator allowed parallel computation, as long as it wasn't exposed to the consumer doing `for await (const item of it)` [14:46:26.0791] But, it's not really going to help much, because your "parallel" 2nd call is going to be gated on whether your source can be iterated in parallel [14:47:00.0793] And if your source is a `async function* foo(){}`, the second `.next()` call won't do anything until the first `.next()` call has settled [14:47:35.0818] * But, it's not really going to help much, because your "parallel" 2nd call is going to be gated on whether your source can be iterated in parallel [14:48:49.0319] So only sync iterable sources (and handwritten async iterable sources) would the `map` iterator to parallelize [14:58:32.0149] * So only sync iterable sources (and handwritten async iterable sources) would get the `map` iterator to parallelize [15:38:10.0893] Justin Ridgewell: no issue for it, this is very last-minute [15:39:36.0826] but: it is true that you can't pump a generator multiple times; nevertheless the change I propose is enough to get parallelism in _iterator helpers_ [15:41:57.0395] i.e.: with the change I propose, if you have ``` async function* foo(){ yield 0; await sleep(1000); yield 1; } let it = foo().map(x => fetch(x)); it.next(); it.next() ``` then the `sleep(1000)` will run in parallel to first call to `fetch`, and if the `sleep` finishes before that `fetch` does, then both `fetch`s can happen in parallel [15:43:04.0371] unless I'm missing something, anyway. so I do not agree with the "only sync iterable sources (and handwritten async iterable sources) would get the `map` iterator to parallelize" claim 2023-01-26 [16:01:43.0471] Unfortunately that doesn't work, async generator functions will not reenter until all previous next calls settle [16:04:44.0502] ``` async function* foo() { yield new Promise(r => setTimeout(r, 1000)); console.log('reenter'); } const it = foo(); it.next(); it.next(); ``` [16:05:03.0789] the previous call settles in my example [16:05:09.0382] The `reenter` won't log until the 1000ms is up [16:05:15.0751] yes but it doesn't need to [16:05:17.0245] my example still works [16:06:07.0096] ``` async function* foo(){ yield 0; await sleep(1000); yield 1; } let it = foo().map(x => fetch(x)); it.next(); // almost immediately after this point, the promise returned from the first call to the underlying foo()'s `.next` settles // then the first call to `fetch` starts it.next() // so this can almost immediately kick off another call to the underlying foo()'s `next`, and have that actually hit the `sleep` ``` [16:06:36.0282] * ``` async function* foo(){ yield 0; await sleep(1000); yield 1; } let it = foo().map(x => fetch(x)); it.next(); // almost immediately after this point, the promise returned from the first call to the underlying foo()'s `.next` settles // then the first call to `fetch` starts it.next() // so this can almost immediately kick off another call to the underlying foo()'s `next`, and have that actually hit the `sleep` ``` [16:08:57.0731] ``` async function* foo(){ yield 0; console.log('reenter'); await sleep(1000); yield 1; } let it = foo(); it.next(); it.next(); console.log('end'); ``` Logs `end` then `reenter`, the first yield doesn't settle the iterator until after a tick [16:09:10.0644] I think you're incorrect, and even if you're correct, you're example will start to fail again as soon as you chain your iterators with async functionality [16:09:50.0751] ``` let it = foo() .map(x => fetch(x)) .map(r => r.json()); ``` [16:09:56.0548] * ``` let it = foo() .map(x => fetch(x)) .map(r => r.json()); ``` [16:10:00.0077] not with the definition of `map` in the slides [16:10:11.0953] I am pretty sure [16:10:17.0475] but I will check after work and get back to you [16:10:22.0712] (couple hours probably) [16:12:22.0837] It depends on how the source iterator performs parallelization [16:14:19.0640] pretty sure it does not [16:15:33.0748] Oh you're right, the second `.map` will work in parallel with the first `.map` [16:16:03.0380] I'm recalling when I was writing `map` as an async-gen function itself, which means I was still hitting backpressure [16:16:27.0843] So only the first iterator chained off a back-pressure iterator will be serial [16:17:09.0533] We still hit the reentrancy issue with an async-gen function, which would be neat to ~solve~change [16:17:22.0816] * We still hit the reentrancy issue with an async-gen function, which would be neat to ~solve~change [19:04:21.0186] Justin Ridgewell: yeah, and that's the problem with the current spec too: the async iterator helpers are implemented as async generators [19:04:53.0904] here's code you can run today which demonstrates that the mapper from the slides can be parallel, both from with itself and with the underlying generator https://gist.github.com/bakkot/a338838aee667517adb03edfa83aaed1 [19:06:59.0767] and indeed the original generator can't be parallel with itself. I don't think there's anything we can do about that, though - the whole point of generators is that they're written as straight-line code, and you can't start executing the next line until the previous one finishes [19:07:09.0144] * here's code you can run today which demonstrates that the mapper from the slides can be parallel, both with itself and with the underlying generator https://gist.github.com/bakkot/a338838aee667517adb03edfa83aaed1 [19:09:14.0701] I think interleaving of async map iterators is expected behavior. [19:09:44.0965] Kris Kowal: what do you mean by interleaving? [19:10:04.0714] just that you can be executing the mapper function in parallel with itself? [19:10:20.0871] (assuming the resulting mapped thing is pumped multiple times) [19:10:48.0456] That effect for one, yes. [19:11:45.0720] > <@bakkot:matrix.org> and indeed the original generator can't be parallel with itself. I don't think there's anything we can do about that, though - the whole point of generators is that they're written as straight-line code, and you can't start executing the next line until the previous one finishes It's not just the running of the generators code, but the settlement of the `next()` promise external from the genrator that blocks [19:11:53.0181] But also map(xf).map(yf) would interleave turns of xf and yf. [19:12:10.0343] Eg, I want to be able to return a `yield fetch()` and reenter without that fetch having settled [19:12:18.0984] > <@jridgewell:matrix.org> It's not just the running of the generators code, but the settlement of the `next()` promise external from the genrator that blocks In the case that you specifically yield a promise, do you mean, or is there another case I'm not thinking of? [19:12:20.0040] ah yeah [19:12:42.0329] with this helper, you could do `yield { v: fetch() }` in your async generator and then `.map(box => box.v)` to get parallelism in the resulting thing (or rather the ability to be parallel assuming someone calls `.next` eagerly) [19:12:55.0076] a little silly but works fine [19:12:56.0665] ... I think [19:13:55.0379] > <@kriskowal:matrix.org> But also map(xf).map(yf) would interleave turns of xf and yf. Right, yeah. You get that with my proposed tweak also (again assuming someone calls `.next()` eagerly) [19:14:09.0163] > <@jridgewell:matrix.org> Eg, I want to be able to return a `yield fetch()` and reenter without that fetch having settled I don’t have spare attention to dig, but I would find it very surprising if `yield fetch()` were equivalent to `yield await fetch()`. [19:15:54.0671] They are, unfortunately [19:16:05.0390] > ``` > async function* foo() { > yield new Promise(r => setTimeout(r, 1000)); > console.log('reenter'); > } > const it = foo(); > it.next(); > it.next(); > ``` [19:16:22.0021] ^ That waits a full second before logging `reenter` [19:17:58.0898] My original map helper was something like `for await (const x of source) yield mapper(x)`, and if `mapper` has any async waiting, it blocks. [19:18:13.0173] here's the slides where we decided that `yield` is `yield await` https://docs.google.com/presentation/d/1U6PivKbFO0YgoFlrYB82MtXf1ofCp1xSVOODOvranBM/edit#slide=id.g223fba4116_0_196 [19:19:00.0793] and notes https://github.com/tc39/notes/blob/55af84ac0ed7a250206849dddd628b2c1db2c9b1/meetings/2017-05/may-25.md#15iva-revisiting-async-generator-yield-behavior [19:19:30.0027] 😦 Before my time [20:39:06.0481] Ah, surprising but maybe for the best. In any case, “overdriving” (drawing concurrent promises from next()) a pipeline of async maps to get parallelism is useful, so glad that’ll work regardless. [20:40:22.0465] In fact, the implicit `await` is doing me a big favor in: ``` function parallel(limit, process) { function *workers() { for (const worker of count(limit)) { yield process(worker); } } return Promise.all(workers()); } ``` [20:44:27.0270] Out of which you can build bounded concurrency pretty easily (I’m taking the liberty of assuming the existence of AsyncIterator.from having not followed along particularly closely) ``` AsyncIterator.prototype.parallelForEach = async (limit, values, process) => parallel(limit, () => AsyncIterator.from(values).forEach(process)); ``` [20:44:44.0216] * Out of which you can build bounded concurrency pretty easily (I’m taking the liberty of assuming the existence of AsyncIterator.from having not followed along particularly closely) ``` Iterator.prototype.parallelForEach = async (limit, values, process) => parallel(limit, () => AsyncIterator.from(values).forEach(process)); ``` [20:44:56.0858] * Out of which you can build bounded concurrency pretty easily (I’m taking the liberty of assuming the existence of AsyncIterator.from having not followed along particularly closely) ``` AsyncIterator.prototype.parallelForEach = async (limit, values, process) => parallel(limit, () => AsyncIterator.from(values).forEach(process)); ``` [21:14:21.0771] > <@kriskowal:matrix.org> In fact, the implicit `await` is doing me a big favor in: > ``` > function parallel(limit, process) { > function *workers() { > for (const worker of count(limit)) { > yield process(worker); > } > } > return Promise.all(workers()); > } > ``` There's no async iterator there and so no implicit `await`; did you mean a different thing? [21:14:50.0512] > <@kriskowal:matrix.org> Out of which you can build bounded concurrency pretty easily (I’m taking the liberty of assuming the existence of AsyncIterator.from having not followed along particularly closely) > > ``` > AsyncIterator.prototype.parallelForEach = async (limit, values, process) => > parallel(limit, () => AsyncIterator.from(values).forEach(process)); > ``` `AsyncIterator.from` works and does the thing you want, yup, though your specification of `parallelForEach` is possibly confused - prototype-placed methods generally want to refer to `this` [21:15:51.0897] > <@bakkot:matrix.org> `AsyncIterator.from` works and does the thing you want, yup, though your specification of `parallelForEach` is possibly confused - prototype-placed methods generally want to refer to `this` Indeed, lost in translation. Local copy wasn’t framed as a prototype method. [21:16:25.0919] > <@bakkot:matrix.org> There's no async iterator there and so no implicit `await`; did you mean a different thing? Oh, indeed. [21:17:56.0740] * Out of which you can build bounded concurrency pretty easily (I’m taking the liberty of assuming the existence of AsyncIterator.from having not followed along particularly closely) ``` const parallelForEach = async (limit, values, process) => parallel(limit, () => AsyncIterator.from(values).forEach(process)); ``` [21:38:57.0150] > If you want the flexibility of not blocking on the async operation before continuing the async generator body, then just don't yield the value yet. 🤔 That doesn't really make sense. [21:39:02.0637] * > If you want the flexibility of not blocking on the async operation before continuing the async generator body, then just don't yield the value yet. 🤔 That doesn't really make sense. [21:55:16.0189] I wonder how difficult it would be to change to Option 2, which would allow easy parallel processing [21:58:40.0704] The only way to consume async iterables: - Callers of `.next()` will await (they already had to, we're returning promises) - for-await-of can be updated to await the inner value - Also call `.return()` if rejection happens [21:59:14.0886] Surprisingly, I don't think there'll be a web-compat risk. [22:02:05.0519] The ways to consume an async iterator: - manual `.next()` - `yield*` (which only works inside another async iterable) - `for-await-of` [22:02:35.0389] I don't imagine manual `.next()` iteration is super common? [22:03:11.0407] So the main way to consume an async iterable is to use `for-await-of`, which we can update to await the inner promise (and cleanup the iterator on rejections) [22:04:07.0816] We can ignore `yield*`, because it's only available in other async iterators, because we still have an async iterator and that still needs to be consumed with `for-await-of`. [22:06:59.0663] `.next()` iteration could be updated to await the inner value, but not block on that settlement [22:08:04.0401] Or maybe we need a `.nextRaw()` which won't unwrap the inner [22:09:17.0196] the main web-compat risk is that `(async function*(){ try { yield Promise.reject(1) } catch (e) { console.log('caught'); } })().next()` prints `caught` [22:09:29.0466] i.e. yielding a rejected promise triggers `catch` handlers around the yield [22:09:36.0243] changing that would be fraught at this point [22:10:22.0345] A `.nextRaw()` then [22:11:54.0527] > <@bakkot:matrix.org> the main web-compat risk is that `(async function*(){ try { yield Promise.reject(1) } catch (e) { console.log('caught'); } })().next()` prints `caught` Actually, how common is manual iteration? [22:13:27.0897] sorry, it's not the `.next` that's relevant here [22:13:35.0723] you'd get the same `caught` in a `for-await-of` [22:13:49.0236] the relevant bit is that the catch handler _inside the async generator_ gets triggered [22:14:39.0675] no idea how common manual iteration is though. my guess would be not especially but there's definitely times you want it, e.g. for queues and stuff [22:37:22.0883] > you'd get the same caught in a for-await-of > the relevant bit is that the catch handler inside the async generator gets triggered I would phrase this as "`.next()` will call `.error()` if promise rejects" [22:37:30.0801] * > you'd get the same caught in a for-await-of > the relevant bit is that the catch handler inside the async generator gets triggered I would phrase this as "`.next()` will call `.error()` if promise rejects" [22:40:29.0586] Eg, the slides say: ```javascript // Given: AsyncIterator.prototype = { async next(v) { return { value: await resumeWithValue(v), done } } async error(e) { return { value: await resumeWithError(e), done } } } // Input async function* foo() { try { yield Promise.reject(1); } finally { console.log(2); } } // Output async function* foo() { try { yield await Promise.reject(1); } finally { console.log(2); } } ``` [22:41:07.0876] I think we could unobservably change that to: [22:42:12.0572] ```javascript // Given: AsyncIterator.prototype = { async next(v) { try { return { value: await resumeWithValue(v), done } } catch (e) { return { value: await resumeWithError(e), done } } } async error(e) { return { value: await resumeWithError(e), done } } } // Input async function* foo() { try { yield Promise.reject(1); } finally { console.log(2); } } // Output has no change ``` [22:43:57.0657] And if we can do that, why can't we do: ```javascript // Given: AsyncIterator.prototype = { async next(v) { try { return { value: await this.nextRaw(v).value, done } } catch (e) { return { value: await resumeWithError(e), done } } } async nextRaw(v) { return { value: resumeWithValue(v), done } } async error(e) { return { value: await resumeWithError(e), done } } } ``` [07:01:21.0314] > <@bakkot:matrix.org> the main web-compat risk is that `(async function*(){ try { yield Promise.reject(1) } catch (e) { console.log('caught'); } })().next()` prints `caught` That implicit `await` is actually surprising to me. [07:05:14.0085] > <@bakkot:matrix.org> no idea how common manual iteration is though. my guess would be not especially but there's definitely times you want it, e.g. for queues and stuff We do quite a bit of manual iteration, and it's error prone. I would expect that calling `.next()` would unwrap / await the inner promise. I don't see a reason to need `nextRaw()` [07:09:05.0626] And yes making the `.next()` implementation trigger the equivalent `.throw(e)` logic on rejection during unwrapping would preserve the consumer behaviors (without needing to update `for-await-of`) [07:11:12.0272] tricky part is that on the producer side, the `throw()` continuation may come in when the generator has already moved on if the consumer called `.next()` without awaiting (manually iterated) [08:19:29.0100] Having `.next` call `.throw` on the generator would be kind of weird - that would inject the throw completion into the async generator at a point unrelated to the place that produced the rejected promise. [08:20:08.0827] > <@mhofman:matrix.org> tricky part is that on the producer side, the `throw()` continuation may come in when the generator has already moved on if the consumer called `.next()` without awaiting (manually iterated) Which I guess is the thing you were saying here, yeah. [08:23:49.0619] I was just trying to analyze Justin Ridgewell suggestion above. In most cases it would behave the same, except for multiple un-awaited `.next()` calls [08:25:26.0980] Regardless of the approach, if we want to make generators not implicitly await, it will be a contract change on the producer side in case the consumer doesn't await itself [08:27:01.0298] And I don't think there's a way to make the producer opt-in somehow, besides foregoing generators of course [08:27:11.0743] * And I don't think there's a way to make the producer opt-in somehow, besides foregoing generators of course [08:28:21.0491] There is the hacky way using my tweak to async iterator helpers: replace `async gen*f(){ yield p; }` with `let f = () => (async gen*f(){ yield { v: p }; })().map(box => box.v)` [08:28:31.0290] * There is the hacky way using my tweak to async iterator helpers: replace `async gen*f(){ yield p; }` with `let f = () => (async gen*f(){ yield { v: p }; })().map(box => box.v)` [08:32:02.0925] yeah true [08:34:14.0270] ok I think that means not changing the generator semantics, and leaving it to the producer opting in these semantics through helpers. I assume the helper would call `.throw()` on the wrapped generator if it encounters a rejection ? [08:38:47.0097] No. Consider the case when you're not doing this box thing, just `.map(f)` - if `f` throws, that's not an error in the producer [08:39:22.0442] If the mapper function throws then `.map` will call `.return` on the producer, in the same way that `for await (let item of iter) { throw 0 }` would call `.return` on the producer, of course [08:39:37.0719] but right now nothing in the language calls `.throw`, and iterator helpers will not change that [08:39:58.0289] * If the mapper function throws then `.map` will call `.return` on the producer, in the same way that `for await (let item of iter) { throw 0 }` would call `.return` on the producer, of course [08:46:20.0685] Oh right I always forget that `.throw()` is only called for `yield *` but not in `for await of` [08:50:24.0361] Should `f` get access to the iterator as a 3rd argument like `array.map()` ? [08:52:15.0525] so that we can do `.map(async (box, i, iter) => { try { return await box.v } catch(e) { iter.throw(e); throw e; } })` ? [08:53:22.0049] wouldn't be very clean tho as the spec would still call `.return` after `.throw` being called by the mapper function ... [08:54:36.0776] Yeah, I think if the producer wants to handle rejected promises itself, it needs to await them [08:54:40.0991] * Yeah, I think if the producer wants to handle rejected promises itself, it needs to await them 2023-01-27 [17:02:12.0097] > <@bakkot:matrix.org> Having `.next` call `.throw` on the generator would be kind of weird - that would inject the throw completion into the async generator at a point unrelated to the place that produced the rejected promise. How so? It'd inject at the `yield` that gave the promise. It should be the same if you're waiting on the `next()`'s return promise to settle [17:03:16.0376] You _could_ advance the generator body by manually calling `next()` again before the settlement, but that's the exact use case I'm trying to enable [17:04:13.0533] Justin Ridgewell: right, but that's kind of the point - if you've manually advanced the generator before settlement, then the throw completion will be injected at the wrong point [17:07:14.0101] We could make it so that `next()`, `throw()`, and `return()` (the normal high-level APIs) all queue with backpressure to prevent that, and `nextRaw()` and the other raw low-level API allow you to manually advance [17:22:50.0435] I'd be against adding yet another method to iterators, especially when `next()` can already have those semantics for iterators that support it. It just happens that an async generator creates and iterator that implicitly awaits the yielded values. Other kind of iterators may not [19:30:25.0991] Notably, async iterators backed by promise lists do carry promise values and do not synchronize their resolution with transportation. 2023-01-30 [11:46:08.0989] Really looking forward to seeing this in the wild Ashley Claymore