-
Notifications
You must be signed in to change notification settings - Fork 30.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
multipleResolves
event should not be triggered when using Promise.all()
or Promise.race()
#24321
Comments
@nodejs/v8 this looks like a bug in V8 to me. It should indeed not be triggered outside of a promise constructor. |
Hm, I'm not 100% sure. Technically you are rejecting a promise multiple times here, so it's appropriate to fire the hook. But I can see how |
Specifically for |
multipleResolves
event should not be triggered when using Promise.all()
multipleResolves
event should not be triggered when using Promise.all()
or Promise.race()
While it is likely possible to fix this specific issue at the v8 level, this does not seem like a v8 level issue. Rather, the Really, this is should just be a documentation fix, that relabels the usage of the event for specific debugging purposes. The existing recommendation to end the process on any such events seem absurd, and poorly thought out. |
@bmeurer would it be possible/make sense to keep emitting the event but with another name so we can differentiate multiple rejects in the Promise executor (or in a thenable callback) vs. Promise.all/Promise.race? |
|
just checking if Promise.all and Promise.rsolve are on the stack isn't adequate. one could do on a much larger level, I think this proves that multiple resolves/rejects aren't actually always programming error, and shouldn't be treated as exceptional. |
Yes, exactly, hence my documentation fix suggestion. This "feature" has been used for ages, e.g. see https://stackoverflow.com/q/20328073/248062 and https://stackoverflow.com/a/18218542/248062. V8 also uses it (as evidenced by this issue), I use it myself, and I'm sure others do as well. |
I think it might make sense to scope the hook to its original intent or |
Talking to @hashseed I see two possible ways:
cc @hashseed |
I agree with @benjamingr that we should limit our scope to solve the original problem, which is to detect rejections after the promise has been resolved, through exceptions in the Promise constructor. For that, we could simply check whether the Promise has been already resolved when we catch an exception inside the Promise constructor. The implementation would be fairly clean and local to the Promise constructor. Option (2) suggested by @bmeurer is a bit broader than that, and would also report multiple calls to the resolve or reject closure. Distinguishing these calls from within the Promise constructor and from outside, which e.g. is the case with Opinions? |
I personally would like a third option: Mark the It is definitely beneficial to know what promises were rejected in a |
@BridgeAR that sounds interesting (providing the users with more information) although unrelated to why we added the hook (it's a nice idea though).
One thing to keep in mind is that a throw - before - reject is also possible a-la: new Promise(resolve => {
setTimeout(resolve, 1000);
cleanup(); // synchronous, but throws exception
}) That's not the "more important" case but it's also interesting to address. |
For those that argue that all uses of multiple resolves is a very bad thing, I would like to present a simple timeout use-case: const fs = require('fs');
let promise = new Promise((resolve, reject) => {
setTimeout(reject, 1000);
fs.readFile(path, (err, data) => err ? reject(err) : resolve(data));
}); In this case, not having to worry about multiple resolves saves a lot of code. |
Option 2 suggested above solves this issue, right? |
I'd argue that example conflates the timeout logic with the reading file logic in the promise constructor. It also rejects with const fs = require('fs').promises;
const delay = require('util').promisify(setTimeout);
let promise = Promise.race([
fs.readFile(path),
delay(1000).then(() => { throw new Error('timed out'); })
]); |
It seems like several solutions were suggested, did you reach a consensus? |
+1 for fix on this. Multiple Resolve is something that considered to be a severe programmer error, while for |
I don't think any action should be taken here until cancellation of Promises is actually worked out. Both the With cancellation there could be something like: import { promises as fs } from "fs"
const signal = new CancelSignal()
let fileRead = Promise.race([
fs.readFile(path, { signal }),
delay(1000, { signal })
], { onResolved: () => signal.cancel() }) Then Note that in order for this to allow for warning (or ending the process) on |
You can already do this (finally pretty much exists for cleanup) const timer = delay(5000)
Promise.race([timer, readFile])
.finally(() => cancel(timer)); |
That doesn't solve the issue of my original point. My point is that the reason observing But However But then this is only useful if everything that returns a Promise is cancellable including Personally I think without synchronous cancellation of everything the |
To be fair, all of the promise events we have are only useful if you make assumptions about the usage patterns of promises, not just |
I agree but unfortunately Node's docs give quite possibly the worst advice I've ever seen: terminate the process on any There doesn't really seem to be a coherent design over these things other than "hey here's some stuff v8 exposes so terminate your program if they happen". I grow tired of bringing it up again and again but I do feel like Node's "design" is comically terrible for actually tracking errors that I feel I need to. But most of these problems are easily solvable by something like Zones as this gives you narrower contexts in which to observe specific errors rather than just the whole program. With something like Zones you could observe Hell I even think something like Zones would be a viable way of exposing propagated cancellation too which would make these sort've things even simpler again. But instead of something with a coherent design like Zones we get madhouse advice of terminating the process when doing common things Promises are explicitly designed to from anywhere in the process. |
Frustratingly as I have just remembered Node.js already has this Zone-esque thing and it's called domains, which are deprecated for reasons not specified in the docs. Why is this deprecated? It appears that it could be extended to solve these problems that these hooks introduce. |
Some info on that topic is in https://nodejs.org/en/docs/guides/domain-postmortem/ |
zones got me curious so i made this https://gist.github.com/devsnek/07a61aec7d9f40d1495048790dd30a8f |
Nice. I wasn't even aware of |
The 'multipleResolves' event does not necessarily indicate an error. This commit makes the documentation less opinionated, and includes potential caveats. PR-URL: nodejs#28314 Refs: nodejs#24321 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
The 'multipleResolves' event does not necessarily indicate an error. This commit makes the documentation less opinionated, and includes potential caveats. PR-URL: #28314 Refs: #24321 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Amazed to discover that this is still an unsolved issue. I just want to mention another possible workaround, depending on your use case. You can use
This almost gives me what I want from |
Just to add a note here from a user of Node.js: To me there are 3 cases we need to consider here:
I'm not here to define if 1. or 2. is a bad design or anything in this direction, but I want to say that 3. might not be a the best case for a developer who identifies himself by the 2. description. Moreover, this might be best caught in user-space, where one would use a custom Note: But I hope agree that triggering this event in a function, which called |
I think we can close this since |
Thanks @Mesteery and @benjamingr 👍 |
Version:
v11.1.0
Platform:
Linux myhost 4.18.0-10-generic #11-Ubuntu SMP Thu Oct 11 15:13:55 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
When using
Promise.all()
orPromise.race()
, several child promises might be legitimately rejected. This might be intended by the developer, so it should not trigger amultipleResolves
process error. This is in contrast to callingreject()
several times innew Promise()
which definitely indicates a programming error.Use case 1: a unit test is checking that an async function returns a rejected promise. The unit test fires the function several times in parallel for performance reason. The test expects each function call to reject a promise. I.e.
multipleResolves
should not be fired.Use case 2: I created a library log-process-errors which logs every process errors (including
multipleResolves
). The goal is to make sure process errors are properly logged in production, so they can be fixed. However this does not work if Node.js is triggering them where there is no programming errors.The text was updated successfully, but these errors were encountered: