I\'m exploring node.js for quite some time now. But yet haven\'t figured out exactly, how a program executes.
For e.g. taking this simple program:
/
Blocking I/O is just that: the call does not return until it has completed. So when you call readFileSync
, it makes calls directly to the OS API to read in a file and only when the entire contents have been copied into a Buffer
does control return to your JS.
With blocking (synchronous) functions, the event loop has absolutely nothing to do with how the I/O is handled. This is why synchronous I/O is very bad in node – it prevents anything else from happening since the event loop is not running.
On the other hand, the normal (asynchronous) I/O functions like readFile
simply put in a request for I/O to be done and then return. Exactly how that I/O is performed depends on the nature of the I/O request (ie whether filesystem or network), but it is all handled by the C library libuv on a separate thread.
Network I/O is actually performed using the host OS's native asynchronous I/O facilities, which is very fast and does not necessitate a separate thread for each connection.
Whenever the OS reports network I/O activity, the event is placed in a queue. On the next tick of the event loop, the event is picked up and dispatched to the appropriate JavaScript function. (C code can obtain a reference to JS functions and invoke them.)
File I/O is done using normal blocking system I/O calls. Each I/O request is placed in the libuv work queue and executed by a thread pool. The key here is that the blocking I/O is done in C, on a thread separate from the JavaScript thread.
When the blocking I/O call returns, the result is placed in a queue. Again, the event is dispatched on the next tick.
Node keeps a reference count of pending work requests – things like I/O, timers, and listening servers. If there are zero at the end of a tick, the process exits. (In some cases, you can explicitly remove an active request from the reference count with unref().)
Some other related answers will help explain some of these concepts further:
Finally, let's walk through your example program. Here's exactly what happens:
require('fs')
gives you a reference to the already-initialized fs module. The builtin modules are special and do not require I/O to load.fs.readFileSync
calls in to the OS file APIs. It does not return until the file contents are copied into memory.fs.readFile
adds an item (containing the filename and a reference to the callback) to the libuv work queue, then returns immediately. (Because there would be nothing on the work queue, the I/O will begin shortly on a separate thread.)fileBuffer
are logged to the console.readFile
.data
are logged to the console and the callback function returns.