20:56 | <Justin Ridgewell> | I lost so much time today due to an unhandled rejection from a promise.finally() call |
20:57 | <Justin Ridgewell> | The promise itself was handled, but the new one doesn't inherit that state. |
21:01 | <Kris Kowal> | Is this an isolated example of the problem you observed? Promise.reject(new Error('Bad from the start')).finally(() => Promise.reject(new Error('Got worse'))) , where the unhandled rejection is Got worse ? |
21:02 | <Kris Kowal> | And you lost time, I presume, because Bad from the start was the problem hiding behind the error? |
21:04 | <Kris Kowal> | If that’s the case, perhaps the mitigation is for finally to produce an AggregateError . |
21:22 | <Ashley Claymore> | Would be great if that was web compat |
21:23 | <Ashley Claymore> | Maybe there is a way to thread that finally rejection to the unhandled exception handler, while preserving the current finally semantics of .then |
21:23 | <Ashley Claymore> | If want to be more compat |
21:24 | <Kris Kowal> | Or…drumroll… promise.finalé() |
21:24 | <Ashley Claymore> | or just .fin 🎥 |
21:25 | <Kris Kowal> | Hah. Q predated Doug’s initiative to make keywords valid property names, so I did call it fin in the earliest versions, and it’s still there for back-compat. |
21:27 | <Kris Kowal> | Shunting the antecedent error out to unhandled rejection handler is a bandaid I can support, but that effectively means deprecating finally , since breaking the causal chain is bad in general. |
21:28 | <Kris Kowal> | There really needs to be a way for both errors to flow through the output promise. |
21:29 | <Kris Kowal> | And as long as we’re talking history, if I’d made Q after Doug won keywordly-named properties, I would have been tempted to name resolve and reject as return and throw . |
21:30 | <bakkot> | wait Justin Ridgewell was the problem that finally swallowed the error from the original promise , or that you had an error thrown in your finally handler and you didn't handle that error, only errors on the original promise ? |
21:30 | <Kris Kowal> | And then we’d be living in a world where it’s obvious that Promises are just a degenerate AsyncIterators. |
21:31 | <Justin Ridgewell> |
|
21:31 | <bakkot> | ah yeah |
21:31 | <bakkot> | that one is tricky |
21:32 | <Justin Ridgewell> | It's not equivalent to
|
21:33 | <Justin Ridgewell> | Which is the behavior I wanted |
21:33 | <Kris Kowal> | So, promise.catch().finally() would have done the job. |
21:34 | <Justin Ridgewell> | Yup, but my catch and finally aren't in the same file |
21:35 | <Kris Kowal> | This is I think a case where promises are working as designed, but we need to promote a culture of not dropping promises, and linting for dropped promises. That’s a bit of a tall order at the moment, since you need something like the TypeScript compiler to find them. |
21:37 | <Kris Kowal> | We’ve used an eslint rule for this at Agoric, but because it’s driven by eslint and not tsc, there’s a great deal of duplicative effort in return type inference, so it’s way too slow. |
21:44 | <bakkot> | TS doesn't currently have a "must use" annotation |
21:44 | <bakkot> | so even TS is not sufficient on its own |
21:45 | <bakkot> | also there's a fundamental problem where any promise handler can throw, and then you have to handle that also |
21:45 | <bakkot> | like just asyncFn() is a bug because you don't handle exceptions, but asyncFn().catch() might also be a bug because you don't handle exceptions in the catch handler, and so on unto infinity |
21:46 | <Kris Kowal> | TypeScript at least knows that () => {} will not throw. |
21:47 | <Kris Kowal> | But yes, no amount of rigor will eliminate the need for UnhandledRejection reports. |
21:48 | <Kris Kowal> | And even TypeScript can’t help you if your handler throws a RangeError . |
21:50 | <Kris Kowal> | TS doesn't currently have a "must use" annotation |
21:52 | <Kris Kowal> | And that is quite good at halting at a handler that has done everything in its power to ensure no exceptions go unhandled. |