The NODE.JS approach is not unique, but the underlying execution model is different from other runtime environments like Python, Ruby, PHP, or Java.
Let’s take a look at the following part of the code:
var result = database.query("SELECT * FROM hugetable"); console.log("Hello World");
The first line queries the database for lots of rows, and the second line puts “Hello World” into the console.
Let’s assume that the database query is really slow due to the number of rows, and it takes too long to execute.
With codes written this way, the JavaScript interpreter of NODE.JS has to read a complete result set from the database and then execute the console.log() function.
If this piece of code were written in PHP, for example, it would work the same way -> “read all the results at once, then execute the next line of the code”. If this code were part of a web page script, the user would have to wait several seconds for this page to load. However, in the PHP execution model, this would not become a “global” problem (i.e. the web server starts its own PHP process for every HTTP request it receives). If one of these requests results in a slow execution of the code, it slows down the page loading only for that particular user and does not affect other users.
The execution model of NODE.JS is different – there is only one single process. If there is a slow database query somewhere in the process, it affects the whole process – everything comes to a halt until the slow query has finished.
To avoid this, JavaScript and therefore NODE.JS introduce a concept of event-driven, asynchronous callbacks by utilizing an event loop.
We can understand this concept by analyzing the re-written version of the problematic code:
database.query("SELECT * FROM hugetable", function(rows) { var result = rows; }); console.log("Hello World");
Instead of expecting database.query() to return the result directly, we pass it the second parameter – an anonymous function.
In the previous form the code was synchronous: “first do the database query, and only when this is done write to the console”.
Now, NODE.JS can handle the database request asynchronously, provided that database.query() is part of the asynchronous library. It takes the query and sends it to the database, but instead of waiting for it to be finished, it makes a ‘mental note’ saying “when at some point in the future the database server is done and sends the result of the query, then I have to execute the anonymous function passed to database.query().”
Then, NODE.JS immediately executes console.log() and enters the event loop. It continuously cycles through this loop repeatedly, even though there is nothing else to do – events such as “slow database query” finally deliver their results.
- Note: This asynchronous, single-threaded, event-driven execution model is not always the only and the best option – it is just one of several models, and it too has its limitations (one being that NODE.JS is just a single process capable of running on one single CPU core). However, this model is quite approachable, because it allows for writing of applications that deal with completion in an efficient and relatively straightforward manner.
You might want to take time to read Felix Geisendörfer’s post on “Understanding NODE.JS” for additional background explanation.