Non-Blocking I/O
One of the biggest advantages of using Node.js over Python or Ruby is that Node has a non-blocking I/O mechanism. To illustrate this, let me use an example of a line in a Starbucks coffee shop. Let’s pretend that each person standing in line for a drink is a task, and everything behind the counter — cashier, register, barista — is a server or server application. When we order a cup of regular drip coffee, like Pike, or hot tea, like Earl Grey, the barista makes it. The whole line waits while that drink is made, and the person is charged the appropriate amount.
Of course, we know that these kinds of drinks are easy to make; just pour the liquid and it’s done. But what about those fancy choco-mocha-frappe-latte-soy-decafs? What if everybody in line decides to order these time-consuming drinks? The line will be held up by each order, and it will grow longer and longer. The manager of the coffee shop will have to add more registers and put more baristas to work (or even stand behind the register him/herself). This is not good, right? But this is how virtually all server-side technologies work, except Node. Node is like a real Starbucks. When you order something, the barista yells the order to the other employee, and you leave the register. Another person gives their order while you wait for your state-of-the-art eye-opener in a paper cup. The line moves, the processes are executed asynchronously and without blocking the queue by waiting.
This is why Node.js blows everything else away (except maybe low-level C/C++) in terms of performance and scalability. With Node, you just don’t need that many CPUs and servers to handle the load.
Asynchronous Way of Coding
Asynchronicity requires a different way of thinking for programmers familiar with Python, PHP, C or Ruby. It’s easy to introduce a bug unintentionally by forgetting to end the execution of the code with a proper return expression.
Here is a simple example illustrating this scenario:
var test = function (callback) {
return callback();
console.log('test') //shouldn't be printed
}
var test2 = function(callback){
callback();
console.log('test2') //printed 3rd
}
test(function(){
console.log('callback1') //printed first
test2(function(){
console.log('callback2') //printed 2nd
})
});
If we don’t use return callback() and just use callback() our string test2 will be printed (test is not printed).
callback1
callback2
tes2
For fun I’ve added a setTimeout()
delay for the callback2 string, and now the order has changed:
var test = function (callback) {
return callback();
console.log('test') //shouldn't be printed
}
var test2 = function(callback){
callback();
console.log('test2') //printed 2nd
}
test(function(){
console.log('callback1') //printed first
test2(function(){
setTimeout(function(){
console.log('callback2') //printed 3rd
},100)
})
});
Prints:
callback1
tes2
callback2
The last example illustrates that the two functions are independent of each other and run in parallel. The faster function will finish sooner than the slower one. Going back to our Starbucks examples, you might get your drink faster than the other person who was in front of you in the line. Better for people, and better for programs! :-)