Contents


Use the Node.js event loop effectively

How to avoid unexpected results in your Node.js apps

Comments

Interactive code: When you see Run at the top of a code sample, you can run the code, check the results, make changes, and run it again.

For new Node.js application developers, part of the learning curve is getting comfortable with how the single-threaded event loop works and how it can lead to unexpected results. You can practice using the event loop in the 3 interactive samples in this tutorial. In no time, you'll be writing fast, efficient code that handles asynchronous calls easily.

Let's look at three simple pieces of code to illustrate how the event loop works.

Sample 1: A simple example

Try icon The first sample defines three functions and calls them. Click Run to run the code. Then try changing the numeric values in the setTimeout() calls to see how the output changes. For example, set all the values to 0.

Show result

You might have expected the original sample to generate this output:

world!
Hello
there,

However, that's not the case. printNow('Hello') executes first, then printSoon('there,'), then printEventually('world!'). That's because when the Node engine sees the setTimeout() in the printEventually() call, it hands it to the OS and then moves on to the call to printNow(). That code is synchronous, so the message Hello prints right away. Finally, the call to setTimeout() in printSoon() is passed to the OS. One second later (printSoon() waits for 1000 milliseconds), the setTimeout() timer in printSoon() expires, so the message there, prints to the console. One second after that, the last setTimeout() expires and world! shows up. That's why the three statements are processed in this order:

Hello
there, 
world!

How the event loop works

Traditional web servers are multithreaded, with each session typically getting its own thread. That approach works, but it requires the web server to allocate resources that sit unused when the session is idle. The overhead of those idle sessions makes it more difficult to scale the server to handle spikes in demand.

The Node engine, on the other hand, has a single thread that handles every notification from the operating system that something is ready to be acted upon. If that thing is asynchronous (a call to a database or a REST interface, for example), the Node engine asks the operating system to notify it when that call is ready for processing (say, when data has arrived from the database or the REST call). In the meantime, the Node event loop moves on to the next thing that needs to be done.

Understand that the Node engine handles everything right away. It's just that in some cases "right away" means asking the operating system to let it know when something is ready for processing. For more information about how the magic happens, see the videos listed in the "Related topics" section at the end of this tutorial.

Sample 2: The callback pattern

Although the first sample illustrates how Node handles asynchronous code, you normally call asynchronous code with the callback pattern. The pattern looks like this:

Listing 1. Pseudocode for defining an asynchronous function
function asyncCode(arg1, arg2, callback) {

  // Do whatever the function does here. When it's complete, it should 
  // invoke the callback function, returning a (hopefully null)  
  // JavaScript Error object and the results of this function. 

  return callback(error, results);
}

The last parameter passed to asyncCode() is another function. When asyncCode() finishes its work, it invokes the callback function passed to it. By convention, the asynchronous function passes a JavaScript Error object as the first parameter to the callback, followed by whatever results the asynchronous function generated. Note that the asyncCode() function can have as many arguments as it needs, and it can pass as many arguments to the callback function as necessary.

That's how to define an asynchronous function. Here's how to call one:

Listing 2. Pseudocode for calling an asynchronous function
asyncCode(x, 37, function(error, results) {
  if (error != null)
    // Handle the error if there is one
  else
    // Otherwise do whatever we want with the results
});

Try icon This version of the code uses callback functions. Click Run to run the code as is. Then try changing the numeric values in the printMessage() calls to see how the output changes. Try replacing console.log('Hello') with another call to printMessage(). What happens if the value of a timeout is zero? Does that affect the order of execution?

Show result

The printMessage() function implements the callback pattern. It sets a timeout, causing Node to pass the timeout to the OS. Node then moves on to the next thing. In this case the next thing is a simple call to console.log(). After that is another call to printMessage(), which sets another timeout. The code wraps up as the timeouts expire and there, and world! are written to the console. The callback functions generate the same message as the first sample:

Hello
there, 
world!

Sample 3: Nesting callbacks

If for some reason you wanted the three words of the message to print in a particular order, you would need to nest the callback functions. For example, if the timeout parameters were randomly generated numbers between 0 and 5000, you would have no way of knowing how your message would turn out.

Try icon Click Run to run the code as is. Now try changing the numeric values in the printMessage() calls. No matter what values you use, the code executes in the same order.

Show result

This code ensures that these three invocations of printMessage() occur in a particular order. The first call to printMessage() passes in a callback function that also calls printMessage(), which in turn passes in another callback function that calls printMessage(). The code generates this garbled greeting:

world!
Hello
there,

The code is relatively easy to read because we ignore error handling and have only one line of code before calling printMessage() again. If we added error handling back to the code and have complex logic between calls, this can quickly lead to callback hell, where code is nested to several levels and difficult to understand. See the "Related topics" section for more on callback hell.

Conclusion

That's a quick look at how to use the Node.js single-threaded event loop. As you use Node libraries to access things like databases and files, knowing how to handle asynchronous methods — and how to ensure that code is executed in a certain order — are crucial skills. With a good understanding of the event loop, you'll be able to write fast, efficient code that handles asynchronous calls easily.

About our interactive samples

The interactive code samples in this tutorial use the developerWorks sandbox. The sandbox is deployed in an OpenWhisk environment running on IBM Bluemix®. Look for lots more interactive content over the coming weeks and months. (And as always, we encourage you to sign up for a free Bluemix account and see what the IBM Cloud platform can do.)

Acknowledgments

Your author would like to thank IBM's Sam Roberts for his help in correcting the errors and misconceptions in the original version of this tutorial. Any remaining falsehoods are solely the fault of the author.


Downloadable resources


Related topics


Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=1044605
ArticleTitle=Use the Node.js event loop effectively
publish-date=04202017