04:07 | <Bakkot> | jmdyck: you had a comment on https://github.com/tc39/ecma262/issues/828 today which disappeared |
04:07 | <Bakkot> | did you delete it on purpose, or is github broken? |
04:08 | <jmdyck> | on purpose. |
04:08 | <jmdyck> | meant to cancel, but comment-and-close button is in the same place |
04:08 | <Bakkot> | yeah, that UI is not great |
15:55 | <jorendorff> | devsnek: hey, i don't know if you saw, but yulia and I asked for a slot on the agenda for iterator helpers next week |
15:55 | <devsnek> | jorendorff: yeah yulia told me 👍🏻 |
15:55 | <jorendorff> | devsnek: ok, cool, I want to make sure there are no surprises for you in this presentation |
15:56 | <jorendorff> | the goal is to get committee approval for a specific specification approach |
15:56 | <devsnek> | how did you end up feeling about |
15:56 | <devsnek> | https://github.com/tc39/proposal-iterator-helpers/issues/86#issuecomment-632768983 |
15:58 | <jorendorff> | devsnek: I like it. I think the specification ends up shorter and cleaner that way |
15:58 | <devsnek> | 🎉 |
15:58 | <jorendorff> | than if we make every one of these its own iterator "class" with three methods |
15:59 | <jorendorff> | it's not drastically shorter and cleaner but the difference is real, fewer steps, fewer spec sections |
15:59 | <devsnek> | and it handles state cleanly |
16:00 | <jorendorff> | devsnek: is it OK to say in a slide that the idea is to pick an approach now, and try for Stage 3 next time? |
16:00 | <devsnek> | yeah definitely not time for stage 3 yet |
16:01 | <jorendorff> | 👍 ok |
16:02 | <jorendorff> | devsnek: the champions will be ok with this? |
16:09 | <devsnek> | they were fine with generators at least |
16:10 | <devsnek> | i'll ping them |
19:15 | <bradleymeck> | has anyone attempted to propose a non-propagating promise handler method? e.g. `Promise.prototype.handle(fn, errfn): void` |
19:15 | <bradleymeck> | i don't see any refs on a quick glance |
19:16 | <TabAtkins> | bradleymeck: What's the difference between `.handle()` and a `.then()` that you just ignore the return value of? |
19:17 | <bradleymeck> | no return value, can't do the evil unhandledRejection dance that .then has, likely wouldn't swallow the error |
19:17 | <TabAtkins> | Ah, hm, kk. |
19:18 | <bradleymeck> | https://twitter.com/bradleymeck/status/1266060854111408129 |
19:18 | <bradleymeck> | see thread leading up to that |
19:18 | <bradleymeck> | basically unused return position of .then is a propagation point for unhandledRejection |
19:19 | <TabAtkins> | yup, got it. |
19:23 | <devsnek> | bradleymeck: where do errors thrown in `fn` go |
19:25 | <bradleymeck> | i presume it just lets it propagate through, since it is on a new tick that would be to w/e the global exception handler is |
19:25 | <bradleymeck> | i certainly have written `p.catch(e => setTimeout(() => {throw e;}))` before and it would go through same path |
19:26 | <bradleymeck> | right now i don't think there is a way to escape the promise swallowing state in raw JS itself? |
19:26 | <devsnek> | so host hook |
19:26 | <bradleymeck> | not a new one |
19:26 | <ljharb> | bradleymeck: what would happen if the handle function threw |
19:26 | <devsnek> | HostHandleErrors or whatever its called |
19:26 | <ljharb> | that'd still be an unhandled rejection |
19:26 | <bradleymeck> | ljharb: it isn't rejecting a promise, it is just throwing |
19:26 | <ljharb> | bradleymeck: right but sync or async |
19:26 | <bradleymeck> | idk what you mean |
19:26 | <devsnek> | oh the report errors hook was removed |
19:27 | <ljharb> | like how would it throw an exception |
19:27 | <ljharb> | it'd just be uncatchable until a global uncaught exception hook? |
19:27 | <ljharb> | one that no JS env is required to provide? |
19:27 | <devsnek> | sure |
19:28 | <ljharb> | that sounds pretty bad to me |
19:28 | <bradleymeck> | ljharb: i mean, you can always wrap stuff in try/catch |
19:28 | <bradleymeck> | this is the same behavior as... many things |
19:28 | <devsnek> | bradleymeck: thinking a bit more, maybe something like `.then().catch().join()` |
19:29 | <ljharb> | not if it's not synchronous |
19:29 | <devsnek> | or `.end()` as a less biased name |
19:29 | <bradleymeck> | ljharb: it shouldn't be sync |
19:29 | <ljharb> | (`.done` is the one with precedent, iirc) |
19:29 | <bradleymeck> | promise state cannot be inspected sync |
19:29 | <ljharb> | bradleymeck: right but you can't wrap everything inside a function that can throw in try/catch |
19:29 | <bradleymeck> | ljharb: ? |
19:29 | <ljharb> | and that boilerplate is not better than "remembering to chain promises" |
19:30 | <bradleymeck> | what do you mean you can't put try/catch around a function |
19:30 | <ljharb> | the `handleFn`, it could throw in default args, or inside a `catch` block |
19:30 | <ljharb> | you can put it around a sync function call |
19:30 | <ljharb> | you can't put it around `p.handle(handleFn)` |
19:30 | <bradleymeck> | that isn't any different from other promise handlers |
19:30 | <devsnek> | i think ljharb is saying |
19:30 | <devsnek> | the problem is missing exceptions |
19:30 | <ljharb> | the other promise handlers always produce a new promise, and thus an opportunity to handle the error |
19:31 | <devsnek> | so adding something that still misses exceptions |
19:31 | <ljharb> | and thus a possible unhandled rejection |
19:31 | <devsnek> | doesn't fix the problem |
19:31 | <ljharb> | adding a promise method that doesn't produce a new promise just solves one problem by creating a larger one |
19:32 | <bradleymeck> | ljharb: the person attaching the handler is responsible for the errors in their handler. I don't understand. |
19:32 | <devsnek> | isn't that true at any level |
19:32 | <bradleymeck> | thats the whole reason you end up with process.nextTick style rethrows in node |
19:33 | <ljharb> | the person calling `.then` is responsible for handling the new promise that's produced. how is that different |
19:33 | <bradleymeck> | it isn't which is why I'm very confused |
19:34 | <bradleymeck> | the behavior delegation is still on the person calling the method to handle |
19:34 | <bradleymeck> | one you have to wrap the returned promise is you want to do things, the other you wrap the handler |
19:34 | <ljharb> | oh sure. if a method returns a promise. then the caller is responsible for handling that promise's possible rejection |
19:34 | <bradleymeck> | if you wrap the handler you can 100% guarantee in both cases no "unhandled" events occur |
19:34 | <ljharb> | the handler might produce a rejected promise tho |
19:35 | <bradleymeck> | not if you wrap that handler |
19:35 | <ljharb> | how do you "wrap" that in a way that's not the same problem you're trying to avoid? |
19:35 | <ljharb> | `new Promise((resolve) => handler())` wraps it, sure, but then you have another potential rejection to handle |
19:35 | <bradleymeck> | p.then(v=> {try { return f(v) } catch (e) {} }, () => {}) |
19:35 | <bradleymeck> | i mean... you *could* write that |
19:36 | <ljharb> | how is that better than `p.then(f).catch()`? |
19:36 | <devsnek> | `.catch(() => {})`* |
19:36 | <ljharb> | no need |
19:36 | <ljharb> | you can even do that as the async function, and prevent your consumer from having possible unhandled rejections |
19:36 | <ljharb> | devsnek: p sure `.then()` and `.catch()` work with no args |
19:37 | <bradleymeck> | i mean that snippet above wouldn't be, but a .handle or something seems to have less need for all of this wrangling of promise propagation of errors |
19:37 | <bradleymeck> | you just asked how you could do it and i gave a way |
19:37 | <ljharb> | right, i'm saying that it seems like it moves the need around at best |
19:38 | <ljharb> | like you had your `p.handle(handler)` above. how do you deal with the asynchronous rejection if `handler` produces a rejection? |
19:38 | <devsnek> | ljharb: if you leave out the argument to catch it defaults to `(v) => throw v` |
19:38 | <bradleymeck> | it creates fewer promises and doesn't have the error swallowing behavior since global hooks for errors do exist without the tick heuristic involved either. it is just a way of expressing intent |
19:38 | <ljharb> | devsnek: oops, you're right |
19:38 | <bradleymeck> | ljharb: handler cannot produce a rejection, the function can throw |
19:38 | <bradleymeck> | there is no place for the rejection to reject |
19:38 | <ljharb> | ps, rofl, i typed `Promise.reject(3).` in the chrome console and immediately got an unhandled rejection warning |
19:39 | <devsnek> | its not wrong |
19:39 | <ljharb> | sure it is, those warnings aren't supposed to appear synchronously, nor before i hit enter |
19:39 | <ljharb> | if it's eagerly evaluating as i type for UX reasons it should be suppressing the warnings until i hit enter |
19:39 | <ljharb> | bradleymeck: handler will be invoked asynchronously |
19:39 | <bradleymeck> | unhandledRejection is a heuristic thing |
19:40 | <ljharb> | right but until i hit enter in the repl the JS engine has no way to know what i'm typing |
19:40 | <bradleymeck> | ljharb: yes? like nextTick, setImmediate, setTimeout, a lot of events, etc. |
19:40 | <ljharb> | bradleymeck: if handler throws asynchronously, there is no way to catch that |
19:40 | <ljharb> | bradleymeck: none of those are in JS. |
19:40 | <bradleymeck> | ljharb: correct? but you can't handle a dropped promise ref either |
19:41 | <ljharb> | wait i'm confused now, which thread is that part of |
19:41 | <ljharb> | indeed, i can't handle a rejected promise when the promise is no longer reachable |
19:41 | <bradleymeck> | i'm only talking about a .handle |
19:41 | <bradleymeck> | my argument is unhandled rejection is a different thing than unhandled exception |
19:42 | <bradleymeck> | and you currently only have a way to propagate unhandled rejection in JS |
19:42 | <ljharb> | i agree with that, notably in node where i don't think an unhandled rejection should crash the process :-) |
19:44 | <devsnek> | bradleymeck: in any case, i think anything that requires a change in behaviour |
19:44 | <devsnek> | is not great |
19:44 | <bradleymeck> | devsnek: change in behavior? |
19:44 | <devsnek> | since the problem is forgetting to do exception handling |
19:44 | <devsnek> | the solution can't involve assuming people don't forget to do something |
19:44 | <bradleymeck> | i don't think we can state we can prevent forgetting exception handling |
19:45 | <bradleymeck> | i'm more interested in the fact that only providing a fulfillment handler even if the promise rejects is stuck in an odd place |
19:45 | <devsnek> | honestly i think the solution will be |
19:45 | <devsnek> | some form of async context thing |
19:46 | <devsnek> | just wrap your app in an async context |
19:46 | <bradleymeck> | so async context is a bit different |
19:46 | <devsnek> | one of the things it can do is tell where a rejected promise came from |
19:46 | <bradleymeck> | there is a different between unhandled rejection which by language default is a noop even if pretty much all hosts have moved it to an event |
19:47 | <bradleymeck> | and unhandled exception which is the form that propagates as some form of error in all the environments |
19:48 | <devsnek> | right |
19:49 | <bradleymeck> | so, even if you wrap things in some kind of context, you still want to disambiguate that |
19:51 | <devsnek> | uncaught exceptions rip through async context |
19:55 | <bradleymeck> | well you can't make an uncaught exception once you are in promise hell |
19:55 | <bradleymeck> | at least not in pure js |
19:56 | <devsnek> | i'm not sure what we're talking about anymore lol |
19:56 | <devsnek> | i was just saying you could do |
19:56 | <devsnek> | `new AsyncContext().onError(error handler)` |
19:57 | <bradleymeck> | i'm stating that if your async context is started up, ala something like otherPromise.then(doThingInAContext) doThingInAContext can never create a unhandled exception in JS |
19:58 | <devsnek> | well the error gets handled in the async context's error handler |
19:58 | <devsnek> | which isn't a promise reaction |
19:58 | <devsnek> | so if that throws its not a rejection |
19:59 | <bradleymeck> | what do you mean |
19:59 | <bradleymeck> | how does it get out of being in the promise handler position and thus a rejection position |
20:03 | <devsnek> | bradleymeck: the async context's error handler isn't a promise reaction |
20:03 | <devsnek> | idk how else to say that |
21:30 | <ljharb> | gut checkL can step 3.e.ii ever fail to define the property? https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor |
21:55 | <Bakkot> | yeah, if the object is frozen |
21:55 | <Bakkot> | or prevent-extension'd |
21:55 | <Bakkot> | or sealed |
21:55 | <ljharb> | ok, and in those cases would you expect the current behavior where it silently avoids the issue? |
21:56 | <Bakkot> | what do you mean by silently avoids? |
21:56 | <ljharb> | i mean that `DefineOwnProperty` returns false when it fails |
21:56 | <ljharb> | so there's no abrupt completion for the ? to unwrap |
21:56 | <ljharb> | so, nothing happens |
21:56 | <ljharb> | ie the note on https://tc39.es/ecma262/#sec-createdataproperty |
21:56 | <ljharb> | (that's why CreateDataPropertyOrThrow exists) |
22:02 | <devsnek> | ljharb: it can still throw |
22:03 | <devsnek> | for example with proxies |
22:04 | <devsnek> | people even use `Object.defineProperty(); return true;` instead of `return Reflect.defineProperty()` because the former gives better error messages |
22:06 | <ljharb> | devsnek: sure but would OrdinarySetWithOwnDescriptor be called with a proxy? |
22:07 | <devsnek> | not sure about proxies |
22:07 | <devsnek> | but its used on lots of exotic objects |
22:08 | <ljharb> | right but the spec for OrdinarySetWithOwnDescriptor right now says that trying to add a property to a frozen/sealed/non-extensible object won't throw, if i'm reading it right |
22:09 | <devsnek> | for the objects defined in the spec i think that's true |
22:10 | <ljharb> | right but like, `Object.freeze(Array.prototype).foo = 3` throws in strict mode |
22:11 | <Bakkot> | if you have a proxy which doesn't handle some trap, it just uses the underlying object's trap |
22:11 | <Bakkot> | `(new Proxy({}, { get defineProperty(){ throw 'no'; } })).a = 0` throws `'no'` via OrdinarySetWithOwnDescriptor 3.d.iv |
22:11 | <Bakkot> | sorry, v3.e.ii |
22:12 | <Bakkot> | *via 3.e.ii |
22:12 | <Bakkot> | I am having difficulty with the typing today |
22:12 | <ljharb> | it throws in practice |
22:12 | <ljharb> | but in the spec text it does not throw |
22:12 | <Bakkot> | the thing I wrote throws in the spec |
22:12 | <ljharb> | oh wait |
22:12 | <ljharb> | right, because `O.[[DefineOwnProperty]]` throws |
22:12 | <ljharb> | ohhh i see |
22:12 | <ljharb> | so even on an ordinary object, it'd throw in the frozen etc case |
22:12 | <ljharb> | but the note on https://tc39.es/ecma262/#sec-createdataproperty says "If it does exist and is not configurable or if O is not extensible, [[DefineOwnProperty]] will return false." |
22:13 | <ljharb> | which isn't an exception |
22:13 | <ljharb> | so how does my above example throw in strict mode? |
22:14 | <Bakkot> | https://tc39.es/ecma262/#sec-putvalue |
22:14 | <Bakkot> | putvalue step 6.c |
22:15 | <devsnek> | speaking of strict references |
22:15 | <ljharb> | ok, so does that mean that `O.[[DefineOwnProperty]]`` won't ever return false in the normal object case (because it won't get there if it would otherwise have), but if a proxy returns false, it'd be silently ignored? |
22:15 | <ljharb> | (in the line i'm talking about in OrdinarySetWithOwnDescriptor, i mean) |
22:16 | <Bakkot> | it does get there? |
22:16 | <ljharb> | Bakkot: what invokes PutValue in the normal frozen object case? |
22:16 | <Bakkot> | assignmentexpression evaluation |
22:16 | <Bakkot> | https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation |
22:17 | <ljharb> | ok, and PutValue invokes [[Set]] |
22:17 | <devsnek> | did the [[Set]] op used to be called [[Put]] |
22:18 | <ljharb> | Bakkot: but then how does assignment end up invoking OrdinarySetWithOwnDescriptor ? |
22:18 | <ljharb> | ah i see, nvm |
22:18 | <Bakkot> | devsnek yup |
22:19 | <Bakkot> | http://es5.github.io/#x8.12.5 |
22:19 | <ljharb> | ok so then the "return `false`" is intentional there, because other code checks it and throws everywhere it happens to be used |
22:19 | <ljharb> | thanks for talking me through that |