Coder Social home page Coder Social logo

Comments (18)

drewfish avatar drewfish commented on July 21, 2024

PR #24 is a topic on async programming, which seems a natural place to describe this.

from docs.

Qard avatar Qard commented on July 21, 2024

I think it makes more sense for it to be reference. The information provided is specific to the behaviour of those functions.

from docs.

a0viedo avatar a0viedo commented on July 21, 2024

I'm in favor of getting better docs for the highly misunderstood timers. process.nextTick doesn't seems to be replaceable by setImmediate simply because setImmediate doesn't have a deterministic behaviour when used with setTimeout but process.nextTick seems to do so. See nodejs/node-v0.x-archive#25788

cc @davepacheco

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

In what situations would it make sense to continue using process.nextTick() over setImmediate()?

from docs.

techjeffharris avatar techjeffharris commented on July 21, 2024

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.

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

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.

from docs.

eljefedelrodeodeljefe avatar eljefedelrodeodeljefe commented on July 21, 2024

@davepacheco would that then mean that the internal use of process.nextTick is historic? Because it's used quite often. If it's that easy core should remove those as a best practice, no?

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

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.

from docs.

trevnorris avatar trevnorris commented on July 21, 2024

@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 listen() is run at the beginning of the event loop, but the listening callback is placed in a setImmediate. Now, unless a hostname is passed binding to the port will happen immediately. Now for the event loop to proceed it must hit the polling phase, which means there is a non-zero chance that a connection could have been received. Allowing the connection event to be fired before the listening event.

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

@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.

from docs.

trevnorris avatar trevnorris commented on July 21, 2024

@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 setImmediate from epoll then it will be the next thing to run. But if I schedule setImmediate from another setImmediate, or a timer, then both timers and I/O will be processed. (it is not a typo that timers are processed after timers; this is the case for using UV_RUN_ONCE which node uses).

To demonstrate how necessary a timing mechanism like nextTick() is, I'll demonstrate the same using v8's own API:

setImmediate(() => process._rawDebug('setImmediate'));

(function runner() {
  Promise.resolve(1).then(runner);
}());

setImmediate will never be printed. This is because Promises internally use a mechanism that operates fundamentally the same way as the nextTickQueue. They did this because the additional overhead of needing to wait until the event loop ran around was too great. This is also a case for node's usage.

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

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.

from docs.

trevnorris avatar trevnorris commented on July 21, 2024

@davepacheco

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?

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 setImmediate() then it's possible the data stream could have received additional data. Instead of having been closed "immediately".

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 nextTick() style. While I could control that timing in my own logic, it's simpler to just use a built-in mechanism that does it for me.

I'm not saying that usage of nextTick() should be advocated. In fact I'd probably just simply put "if you're not sure whether you should use setImmediate() or nextTick() then you should use setImmediate()". I am saying that there are real use cases for it that a few people may need. So it should remain a public API.

from docs.

davepacheco avatar davepacheco commented on July 21, 2024

@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.

from docs.

trevnorris avatar trevnorris commented on July 21, 2024

@davepacheco So it's easier to implement logic that handles proper event ordering than simply calling process.nextTick(fn)?

from docs.

techjeffharris avatar techjeffharris commented on July 21, 2024

Pushed an update to nodejs/node#4936, PTAL

from docs.

ajihyf avatar ajihyf commented on July 21, 2024

In the current documentation, it says if you move the two calls within an I/O cycle, the immediate callback is always executed first and gives a code snippet:

// 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 callback(immediate in the check phase of the first event loop and timeout in the timer phase of the second event loop). But the code below doesn't work as expected:

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?

from docs.

Trott avatar Trott commented on July 21, 2024

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.

from docs.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.