Skip to content

Instantly share code, notes, and snippets.

@caike
Last active August 1, 2022 03:57
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caike/5588918 to your computer and use it in GitHub Desktop.
Save caike/5588918 to your computer and use it in GitHub Desktop.
post about Node.js, non-blocking I/O and the event loop

#Node.js and the Event Loop

Node.js is a framework for writing server-side JavaScript applications. It is built on top of the V8 JavaScript runtime and uses an event-driven, non-blocking I/O model that makes it perfectly suited for data-intensive real-time applications.

This blog post will describe what non-blocking I/O means and how working with the event loop can help your applications be more efficient.

##The Restaurant

"This [non-blocking I/O] model simplifies access to slow resources in a scalable way that is intuitive to JavaScript programmers and easy to learn for everyone else." - Node Up and Running.

In order to understand non-blocking I/O, let's picture a common scenario. Suppose you are at a restaurant with friends. A typical experience at a restaurant would be something like this:

  1. You sit at a table and the server grabs your order for drinks.
  2. The server goes back to the bar and passes your order to a bartender.
  3. While the bartender is working on your order, the server moves on to grab another table's order for drinks.
  4. The server goes back to the bar and passes along the other table's order.
  5. Before the server brings back your drinks, you go ahead order some food.
  6. Server passes your food order to the kitchen.
  7. Your drinks are ready now, so the server picks them up and brings them back to your table.
  8. The other table's drinks are ready, so the server picks them up and takes them to the other table.
  9. Finally your food is ready, so server picks it up and brings it back to your table.

A restaurant server taking orders

Basically every interaction with the server follows the same pattern. First, you order something. Then, the server goes on to process your order and returns it to you when it's ready.

Once the order is handed off to the bar or kitchen, the server is free to get new orders or to resume previous orders that happen to be completed. Notice that at no point in time is the server doing more than one thing. They can only process one request at a time.

This is pretty much how non-blocking Node.js applications work. In Node, your application code is like a restaurant server processing orders, and the bar/kitchen is the operating system handling your I/O calls.

I/O calls

Your single-threaded JavaScript application is responsible for all the processing up to the moment it requires I/O. Then, it hands the work off to the operating system which takes care of processing the rest.

##What is I/O ?

Once an application has started, it is loaded into the machine's memory. That's what the CPU will mostly use for running your program. I/O is shorthand for Input and Output and it means accessing anything outside of your application.

Accessing memory is pretty fast, hence a lot of caching mechanisms simply use RAM to store data. However, applications will often need to access the network or read from a text file, and these types of I/O are by far the slowest types. That's where non-blocking I/O really shines.

##Blocking I/O

Back to our restaurant example, if every time the server got an order request they had to wait for the bar/kitchen to finish before taking the next request, then the service for this restaurant would be very slow and clients would most likely be unsatisfied. This is how blocking I/O works.

Consider the following code example:

// requesting drinks for table 1 and waiting...
var drinksForTable1 = requestDrinksBlocking(['Coke', 'Iced Tea', 'Water']);
// once drinks are ready, then server takes order back to table.
serveOrder(drinksForTable1);
// once order is delivered, server moves on to another table.

// requesting drinks for table 2 and waiting...
var drinksForTable2 = requestDrinksBlocking(['Beer', 'Whiskey', 'Vodka']);
// once drinks are ready, then server takes order back to table.
serveOrder(drinksForTable2);
// once order is delivered, server moves on to another table.

// requesting food for table 1 and waiting..
var foodForTable1 = requestFoodBlocking(['Hamburger', 'Salad', 'Pizza']);
// once food is ready, then server takes order back to table.
serveOrder(foodForTable1);
// once order is delivered, server moves on to another table.

In the previous example, both the requestDrinksBlocking and requestFoodBlocking functions perform some sort of blocking I/O call, and the whole process stops while it waits for the I/O operation to finish. Good thing this is not how most restaurants operate!

##Non-Blocking I/O

Our previous example re-written for a non-blocking I/O model allows for multiple I/O calls to be performed without halting the execution of the program. Instead, these calls run independently.

// requesting drinks for table 1 and moving on...
requestDrinksNonBlocking(['Coke', 'Iced Tea', 'Water'], function(drinks){
  return serveOrder(drinks);
});

// requesting drinks for table 2 and moving on...
requestDrinksNonBlocking(['Beer', 'Whiskey', 'Vodka'], function(drinks){
  return serveOrder(drinks);
});

// requesting food for table 1 and moving on...
requestFoodNonBlocking(['Hamburger', 'Salad', 'Pizza'], function(food){
  return serveOrder(food);
});

The callback functions passed as arguments are invoked asynchronously when the return value from their respective I/O calls are available. This looks like a much better restaurant to go to!

##The Event Loop

Now that we've seen how to write non-blocking Node.js code, let's take a look at the missing piece of the puzzle. This is the part responsible for actually invoking the callbacks: the event loop.

When non-blocking I/O calls are complete and their return value is available, they emit an event.

Node's approach to handling these events leverages a well known feature in JavaScript: functions as first class citizens. As we've seen in our previous example, we simply pass functions as callback arguments to Node's non-blocking functions. These callbacks are invoked when the return value from their current I/O calls are available and the proper events are emitted.

Suppose there is a ready event that is emitted when an order is ready. Another way to write the previous code would be to explicitly listen to the ready event.

// requesting drinks for table 1 and listening to the 'ready' event.
var requestDrinksTable1 = requestDrinksNonBlocking(['Coke', 'Iced Tea', 'Water']);
requestDrinksTable1.on('ready', function(drinks){
  return serveOrder(drinks);
});

// requesting drinks for table 2 and listening to the 'ready' event.
var requestDrinksTable2 = requestDrinksNonBlocking(['Beer', 'Whiskey', 'Vodka']);
requestDrinksTable2.on('ready', function(drinks){
  return serveOrder(drinks);
});

// requesting food for table 1 and listening to the 'ready' event.
var requestFoodTable1 = requestFoodNonBlocking(['Hamburger', 'Salad', 'Pizza']);
requestFoodTable1.on('ready', function(food){
  return serveOrder(food);
});

In Node, all objects that listen to or emit events, inherit from EventEmitter.

##Blocking the Event Loop

Applications written in Node run on a single-thread. This means that they can only do one thing at a time, just like our restaurant server.

Let's say that when your food order is ready, the server needs to count the total number of beans on your lentil soup:

requestFoodNonBlocking('Lentil Soup', function(soup){
  countNumberOfBeans(soup); //blocks...
  return serveOrder(soup);
});

The server is able to pass along your food order to the kitchen in a non-blocking fashion and then proceed to do other things. However, once your order is back, they start to count the number of beans on your soup and simply block. They won't be able to do anything else until they are done counting the beans.

If for some reason countNumberOfBeans(soup) results in an infinite loop, then the server is stuck there forever and you will be forced to look for food some place else.

Conclusion

Writing server-side JavaScript in Node.js is easy and fun. It doesn't take much to get up to speed and start writing efficient, non-blocking applications that can handle heavy traffic.

Being single-threaded means that Node.js programs can only do one thing at a time, so it is important to understand how to properly work with the event loop in order to take full advantage of the platform and avoid common pitfalls. For applications that require heavy computation and not much I/O, Node might not be your best choice.

If you want to learn more about Node.js, be sure to check out the Real Time Web With Node.js course over at CodeSchool.

@yalamber
Copy link

if our countNumberOfBeans function accepts the callback which will in return serveOrder it won't block the event loop right?

@WokaFrenk
Copy link

gid:KyygfUHtuQDVkbPnDcRkEd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment