-
Notifications
You must be signed in to change notification settings - Fork 31
docs: improving documentation of setTimeout/setImmediate/process.nextTick #34
Comments
PR #24 is a topic on async programming, which seems a natural place to describe this. |
I think it makes more sense for it to be reference. The information provided is specific to the behaviour of those functions. |
I'm in favor of getting better docs for the highly misunderstood timers. cc @davepacheco |
In what situations would it make sense to continue using process.nextTick() over setImmediate()? |
I watched a NodeSource "Need to Node" presentation hosted by @trevnorris called "Event Scheduling and the Node.js Event Loop" that very clearly explained the nuances and gotchas (setImmediate and nextTick should have their names swapped to be conceptually accurate, but they are likely not to change for a long time if ever due to historical precedent). I'll put together a transcription of that presentation to help clarify the behaviors of the different timers. |
I believe I understand the behavior (though more clarification is certainly helpful -- I tried to describe it for the official docs under nodejs/node-v0.x-archive#6725), but I'm not aware of any situations where nextTick() is required where the same behavior cannot be more clearly written with setImmediate(). A concrete example would be very helpful. |
@davepacheco would that then mean that the internal use of |
Well, I may be wrong about that, and that's why I'm looking for cases where nextTick is considered necessary. But yes, I wonder if many instances of nextTick() could be replaced with setImmediate(). I suspect that would be more complicated than simply replacing the function call because in many cases the code as written today depends on the fact that such events are processed sooner. That doesn't mean nextTick() is actually necessary, just that it requires more significant refactoring than just replacing the function call. I've also heard performance thrown out as a reason to prefer nextTick(), and that may be why it's used internally. But I'm not sure what data supports that, and I suspect that for most users of Node, the added complexity in their own code is not worth it. |
@davepacheco At times it's necessary to allow a callback to run before the event loop proceeds. One example is to match user's expectations. Simple example: var server = net.createServer();
server.on('connection', function(conn) { });
server.listen(8080);
server.on('listening', function() { }); Say that |
@trevnorris Thanks for that example. In that case, all of the event management is handled in the "server" class, and it seems like that class could ensure that 'listening' has been emitted before emitting 'connection' (e.g., by checking a flag and queueing any 'connection' events, for example), right? All things being equal, that implementation would seem clearer to me than having two tiers of "immediate" events. |
@davepacheco That logic would then need to be properly implemented across any class where that type of race condition can occur. Which, in node, is almost all of them. Instead, simply having a timing mechanism of "run after the call stack has unwound but before the event loop continues" is a simple way to queue callbacks that run asynchronously and prevent that race from occurring. Also we're not recognizing the need for timing consistency. If I schedule a To demonstrate how necessary a timing mechanism like setImmediate(() => process._rawDebug('setImmediate'));
(function runner() {
Promise.resolve(1).then(runner);
}());
|
Do you think this problem is unique to core code, where it's possible to perform actions like binding a new file descriptor synchronously as you described in your example? I've managed to never hit this writing my own code outside of core, but I wouldn't be surprised if this issue only happens when using the internal bindings that core uses. I'm also not sure what prevents the timing problem you describe from being recursive. Why is it never necessary to enqueue an event that will happen before the nextTick() handlers? Or before the handlers that happen before the nextTick handlers, and so on? I'm not making any recommendations for core code because I haven't reviewed much of it. In all the cases I have seen, though, including your example, I felt the code was more clearly written with setImmediate() and explicitly coding the desired behavior, rather than relying on the subtle timing semantics of an interface like nextTick(). I don't think implementing the logic in multiple places is so bad, either. Common logic can be abstracted into common code. And as I said, I don't believe I've run into this, so I'm not sure it would be commonly needed outside core anyway. But the reason we've gotten here is because many users have expressed a lot of confusion about this interface. I'm seriously doubtful that the existence of nextTick() as a public interface, either to implement those implicit semantics or for performance, is worth all that confusion. |
One more important usage is in error handling. In conjunction with the ideal of maintaining that all events are asynchronous, it's important that errors are reported before the event loop is allowed to continue. Say, for example, your interface is working with a data stream. There is an error in the data stream that must be reported. The error passed to the event indicates that the data stream should be terminated. If that error has been propagated through Also if I'm writing a native module that creates an interface, which has the same timing constraints as creating a net server, in that it may happen immediately or it may not, for proper propagation of events it's important that the "connection", "listening", etc. event be emitted I'm not saying that usage of |
@trevnorris That example is another case where ordering of events is important, but nextTick() is not strictly required to implement it. I don't believe it really is simpler to use it. As for the action items at hand: I've submitted my suggested docs and weighed in a bunch on why, but obviously it's not my decision. |
@davepacheco So it's easier to implement logic that handles proper event ordering than simply calling |
Pushed an update to nodejs/node#4936, PTAL |
In the current documentation, it says // timeout_vs_immediate.js
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('timeout');
}, 0);
setImmediate(() => {
console.log('immediate');
});
}); I think it's the same if we put the two timers into another setTimeout(() => { // timer a
setTimeout(() => { // timer b
console.log('timeout');
}, 0);
setImmediate(() => { // timer c
console.log('immediate');
});
}, 0); The order in which the two timers inside timer-a are executed is non-deterministic. Did I misunderstand the event loop? |
Closing as this repository is dormant and likely to be archived soon. If this is still an issue, feel free to open it as an issue on the main node repository. |
There are several outstanding issues in joyent/node requesting better documentation
of setTimeout, setImmediate, and process.nextTick. Opening this to consolidate the
reports and track status. Will add links to the original issues here, but closing those
original issues.
The text was updated successfully, but these errors were encountered: