Node.js fundamentals
Node.js is a single-threaded technology. This means that every request is processed in only one thread. In other languages, for example, Java, the web server instantiates a new thread for every request. However, Node.js is meant to use asynchronous processing, and there is a theory that doing this in a single thread could bring good performance. The problem of the single-threaded applications is the blocking I/O operations; for example, when we need to read a file from the hard disk to respond to the client. Once a new request lands on our server, we open the file and start reading from it. The problem occurs when another request is generated, and the application is still processing the first one. Let's elucidate the issue with the following example:
var http = require('http'); var getTime = function() { var d = new Date(); return d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() + ':' + d.getMilliseconds(); } var respond = function(res, str) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(str + '\n'); console.log(str + ' ' + getTime()); } var handleRequest = function (req, res) { console.log('new request: ' + req.url + ' - ' + getTime()); if(req.url == '/immediately') { respond(res, 'A'); } else { var now = new Date().getTime(); while(new Date().getTime() < now + 5000) { // synchronous reading of the file } respond(res, 'B'); } } http.createServer(handleRequest).listen(9000, '127.0.0.1');
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
The http
module, which we initialize on the first line, is needed for running the web server. The getTime
function returns the current time as a string, and the respond
function sends a simple text to the browser of the client and reports that the incoming request is processed. The most interesting function is handleRequest
, which is the entry point of our logic. To simulate the reading of a large file, we will create a while
cycle for 5 seconds. Once we run the server, we will be able to make an HTTP request to http://localhost:9000
. In order to demonstrate the single-thread behavior we will send two requests at the same time. These requests are as follows:
- One request will be sent to
http://localhost:9000
, where the server will perform a synchronous operation that takes 5 seconds - The other request will be sent to
http://localhost:9000/immediately
, where the server should respond immediately
The following screenshot is the output printed from the server, after pinging both the URLs:
As we can see, the first request came at 16:58:30:434
, and its response was sent at 16:58:35:440
, that is, 5 seconds later. However, the problem is that the second request is registered when the first one finishes. That's because the thread belonging to Node.js was busy processing the while
loop.
Of course, Node.js has a solution for the blocking I/O operations. They are transformed to asynchronous functions that accept callback. Once the operation finishes, Node.js fires the callback, notifying that the job is done. A huge benefit of this approach is that while it waits to get the result of the I/O, the server can process another request. The entity that handles the external events and converts them into callback invocations is called the event
loop. The event
loop acts as a really good manager and delegates tasks to various workers. It never blocks and just waits for something to happen; for example, a notification that the file is written successfully.
Now, instead of reading a file synchronously, we will transform our brief example to use asynchronous code. The modified example looks like the following code:
var handleRequest = function (req, res) { console.log('new request: ' + req.url + ' - ' + getTime()); if(req.url == '/immediately') { respond(res, 'A'); } else { setTimeout(function() { // reading the file respond(res, 'B'); }, 5000); } }
The while
loop is replaced with the setTimeout
invocation. The result of this change is clearly visible in the server's output, which can be seen in the following screenshot:
The first request still gets its response after 5 seconds. However, the second one is processed immediately.