-
Notifications
You must be signed in to change notification settings - Fork 6
Process exits with unresolved promises #16
Comments
big terminology change: promises are never "running". promises don't do things. they have things done to them. to keep a good mental model I'd suggest saying unresolved or unsettled, depending on which one you mean. first one makes no sense because it would just keep node alive forever, since nothing else is happening. third one is also untenable, since it's absolutely a valid use of promises to not resolve one, and I suspect it happens a lot. an unresolved promise is basically an uncalled callback. I would, however, be interested in working on a --trace-unresolved--promises option, given that the output wouldn't be too spammy in large projects. We will probably need to collect more data on how often promises are left unresolved. |
The warning shouldn't happen when the process exits, but when the resolver functions of the unresolved promise are getting garbage collected. |
I 100% agree with @devsnek. As a side note, I've been haunted by a "cb not called" issue in NPM for 8 months. |
@mcollina same lmao |
I mentioned in another thread I'd be in favor of a --strict option that turns on options like this. |
Out of curiosity, what is a use case where you would have an unresolved promise and not want the process to live indefinitely? |
@dfoverdx i don't think doing |
@ljharb When would you write that code except to make the app unexitable? How is writing that code any different in intention than writing |
|
|
I just figured out why this must be the case. let p1 = Promise.resolve(1),
p2 = new Promise(() => {}).then(Promise.resolve(2)),
p3 = Promise.race([p1, p2]).then(console.log);
|
more generally, promises are basically fancy callbacks. If you stop a node http server, even though there are event listeners set up to handle requests (like the callback in then()), you would except the process to exit, because nothing is keeping the process alive to call those event handlers (or call the .then() callbacks). |
I just noticed the following weird behaviour (at least weird for me) where the following code exits immediately without setting a timer (if the timer was set, the process wouldn't exit since it's const wait = () => new Promise((resolve) => setTimeout(resolve, 5000))
(async () => {
await wait();
})() The following code waits for 5 seconds before exiting. const wait = () => new Promise((resolve) => setTimeout(resolve, 5000))
const something = (async () => {
await wait();
})() The only difference is that I assign the returned promise to a variable |
@deepal Did you mix up the two samples? I think it should be reversed according to some local testing. Before I spoil the answer: Prettier or another auto-format tool is invaluable when writing JavaScript. This is what happens to your second code sample after prettier ran: const wait = () =>
new Promise(resolve => setTimeout(resolve, 5000))(async () => {
return wait();
})(); The answer is: Removing the assignment revealed a missing semicolon in the first line, changing your code to a noop. :) |
Yeah, I mixed up the samples earlier and corrected afterwards 😬. I then added semicolons and could see for myself what you just explained. Thanks a lot. 👏👏 |
I just ran into a bug in Archiver.js where it exits unexpectedly if you forget to It took me over an hour to realize that the process was exiting because the event loop became empty. I wish there were a And it seems like it would be possible for Node to print a warning if the event loop becomes empty while there's an unresolved promise (at least when a debug flag is turned on), which would be super helpful. Imagine a novice dev, who doesn't know anything about the event loop, running into an issue like this...I don't think they would ever understand what's going on. |
I use https://www.npmjs.com/package/wtfnode from time to time. Might be nice to integrate that into core. |
as far as I can tell wtfnode wouldn't have helped me debug that issue, but I guess I can try it out |
Agree with @bergus that the problem can be generalized. It would be very useful to output a warning whenever the promise's resolver callbacks have been both GC'ed (or the event loop is done) so the user knows that promise will never settle. But yeah, never resolving a promise is actually valid, so... |
How often have we seen this issue? We can log unsettled promises on exit to stderr - but I'm really not sure we should. |
With a switch though, not by default... What would have helped me is a switch to log the cause of exit (e.g. event loop becoming empty) more than logging unresolved promises at exit. |
(The event loop became empty) doesn't really tell you anything, because that's what always causes Node to exit (except for special cases like |
node exits process if unresolved promises are present, for example, listening for a never fired DOM event in jsdom will terminate the process with no errors given, also see nodejs/promises-debugging#16
A lot of users struggle understanding why a Node.js process exits while a promise is
still runningnot yet resolved (see e.g., nodejs/node#22088).This is by design but the question is if it's really the best for most users. It definitely complicates debugging some cases.
I wonder if it makes sense to try to improve the situation for users by e.g., adding an opt-in to either
still runningunresolved (likely a bad idea)still runningunresolved promisesrunningunresolvedAdding such an opt-in feature would likely help debugging never resolving promises and stream based promises where the stream does not receive any data.
Example:
Stream based example: nodejs/node#22088 (comment)
@nodejs/promises-debugging any opinions about this?
The text was updated successfully, but these errors were encountered: