Overview of Blocking vs Non-Blocking
Table of Contents
"I/O" refers primarily to interaction with the system's disk and network supported by libuv.
All of the I/O methods in the Node.js standard library provide asynchronous
versions, which are non-blocking, and accept callback functions. Some
methods also have blocking counterparts, which have names that end with
Blocking methods execute synchronously and non-blocking methods execute asynchronously.
Using the File System module as an example, this is a synchronous file read:
And here is an equivalent asynchronous example:
Let's expand our example a little bit:
And here is a similar, but not equivalent asynchronous example:
In the first example above,
console.log will be called before
the second example
can continue and
moreWork() will be called first. The ability to run
moreWork() without waiting for the file read to complete is a key design
choice that allows for higher throughput.
As an example, let's consider a case where each request to a web server takes 50ms to complete and 45ms of that 50ms is database I/O that can be done asynchronously. Choosing non-blocking asynchronous operations frees up that 45ms per request to handle other requests. This is a significant difference in capacity just by choosing to use non-blocking methods instead of blocking methods.
The event loop is different than models in many other languages where additional threads may be created to handle concurrent work.
There are some patterns that should be avoided when dealing with I/O. Let's look at an example:
In the above example,
fs.unlinkSync() is likely to be run before
fs.readFile(), which would delete
file.md before it is actually read. A
better way to write this, which is completely non-blocking and guaranteed to
execute in the correct order is:
The above places a non-blocking call to
fs.unlink() within the callback of
fs.readFile() which guarantees the correct order of operations.