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>
const promise = Promise.reject();
promise.catch(() => { /* handled */ });
promise.finally(() => {}) // new promise is unhandled
21:31
<bakkot>
ah yeah
21:31
<bakkot>
that one is tricky
21:32
<Justin Ridgewell>

It's not equivalent to

try {
  try { await promise }
  catch { /* handled */ } 
} finally {
}
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
To be clear, TypeScript + ESlint are able to check for dropped promises, but in this form, it’s just too slow. https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/no-floating-promises.md
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.