Table of Contents for
Node.js 8 the Right Way

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Node.js 8 the Right Way by Jim Wilson Published by Pragmatic Bookshelf, 2018
  1. Title Page
  2. Node.js 8 the Right Way
  3. Node.js 8 the Right Way
  4. Node.js 8 the Right Way
  5. Node.js 8 the Right Way
  6.  Acknowledgments
  7.  Preface
  8. Why Node.js the Right Way?
  9. What’s in This Book
  10. What This Book Is Not
  11. Code Examples and Conventions
  12. Online Resources
  13. Part I. Getting Up to Speed on Node.js 8
  14. 1. Getting Started
  15. Thinking Beyond the web
  16. Node.js’s Niche
  17. How Node.js Applications Work
  18. Aspects of Node.js Development
  19. Installing Node.js
  20. 2. Wrangling the File System
  21. Programming for the Node.js Event Loop
  22. Spawning a Child Process
  23. Capturing Data from an EventEmitter
  24. Reading and Writing Files Asynchronously
  25. The Two Phases of a Node.js Program
  26. Wrapping Up
  27. 3. Networking with Sockets
  28. Listening for Socket Connections
  29. Implementing a Messaging Protocol
  30. Creating Socket Client Connections
  31. Testing Network Application Functionality
  32. Extending Core Classes in Custom Modules
  33. Developing Unit Tests with Mocha
  34. Wrapping Up
  35. 4. Connecting Robust Microservices
  36. Installing ØMQ
  37. Publishing and Subscribing to Messages
  38. Responding to Requests
  39. Routing and Dealing Messages
  40. Clustering Node.js Processes
  41. Pushing and Pulling Messages
  42. Wrapping Up
  43. Node.js 8 the Right Way
  44. Part II. Working with Data
  45. 5. Transforming Data and Testing Continuously
  46. Procuring External Data
  47. Behavior-Driven Development with Mocha and Chai
  48. Extracting Data from XML with Cheerio
  49. Processing Data Files Sequentially
  50. Debugging Tests with Chrome DevTools
  51. Wrapping Up
  52. 6. Commanding Databases
  53. Introducing Elasticsearch
  54. Creating a Command-Line Program in Node.js with Commander
  55. Using request to Fetch JSON over HTTP
  56. Shaping JSON with jq
  57. Inserting Elasticsearch Documents in Bulk
  58. Implementing an Elasticsearch Query Command
  59. Wrapping Up
  60. Node.js 8 the Right Way
  61. Part III. Creating an Application from the Ground Up
  62. 7. Developing RESTful Web Services
  63. Advantages of Express
  64. Serving APIs with Express
  65. Writing Modular Express Services
  66. Keeping Services Running with nodemon
  67. Adding Search APIs
  68. Simplifying Code Flows with Promises
  69. Manipulating Documents RESTfully
  70. Emulating Synchronous Style with async and await
  71. Providing an Async Handler Function to Express
  72. Wrapping Up
  73. 8. Creating a Beautiful User Experience
  74. Getting Started with webpack
  75. Generating Your First webpack Bundle
  76. Sprucing Up Your UI with Bootstrap
  77. Bringing in Bootstrap JavaScript and jQuery
  78. Transpiling with TypeScript
  79. Templating HTML with Handlebars
  80. Implementing hashChange Navigation
  81. Listing Objects in a View
  82. Saving Data with a Form
  83. Wrapping Up
  84. 9. Fortifying Your Application
  85. Setting Up the Initial Project
  86. Managing User Sessions in Express
  87. Adding Authentication UI Elements
  88. Setting Up Passport
  89. Authenticating with Facebook, Twitter, and Google
  90. Composing an Express Router
  91. Bringing in the Book Bundle UI
  92. Serving in Production
  93. Wrapping Up
  94. Node.js 8 the Right Way
  95. 10. BONUS: Developing Flows with Node-RED
  96. Setting Up Node-RED
  97. Securing Node-RED
  98. Developing a Node-RED Flow
  99. Creating HTTP APIs with Node-RED
  100. Handling Errors in Node-RED Flows
  101. Wrapping Up
  102. A1. Setting Up Angular
  103. A2. Setting Up React
  104. Node.js 8 the Right Way

Pushing and Pulling Messages

So far we’ve worked with two major message-passing patterns: publish/subscribe and request/reply. ØMQ offers one more pattern that’s sometimes a good fit with Node.js—push/pull (PUSH/PULL).

Pushing Jobs to Workers

The PUSH and PULL socket types are useful when you have a queue of jobs that you want to assign among a pool of available workers.

Recall that with a PUB/SUB pair, each subscriber will receive all messages sent by the publisher. In a PUSH/PULL setup, only one puller will receive each message sent by the pusher.

A PUSH socket will distribute messages in a round-robin fashion to connected sockets, just like a DEALER. But unlike the DEALER/ROUTER flow, there is no backchannel. A message traveling from a PUSH socket to a PULL socket is one-way; the puller can’t send a response back through the same socket.

Here’s a quick example showing how to set up a PUSH socket and distribute 100 jobs. Note that the example is incomplete—it doesn’t call bind or connect—but it does demonstrate the concept:

 const​ pusher = zmq.socket(​'push'​);
 
 for​ (​let​ i = 0; i < 100; i++) {
  pusher.send(JSON.stringify({
  details: ​`Details about job ​${i}​.`
  });
 }

And here’s an associated PULL socket:

 const​ puller = zmq.socket(​'pull'​);
 
 puller.on(​'message'​, data => {
 const​ job = JSON.parse(data.toString());
 // Do the work described in the job.
 });

Like other ØMQ sockets, either end of a PUSH/PULL pair can bind or connect. The choice comes down to which is the stable part of the architecture.

Using the PUSH/PULL pattern in Node.js brings up a couple of potential pitfalls hidden in these simple examples. Let’s explore them, and what to do to avoid them.

The First-Joiner Problem

The first-joiner problem is the result of ØMQ being so fast at sending messages and Node.js being so fast at accepting them. Since it takes time to establish a connection, the first puller to successfully connect can often pull many or all of the available messages before the second joiner has a chance to get into the rotation.

To fix this problem, the pusher needs to wait until all of the pullers are ready to receive messages before pushing any. Let’s consider a real-world scenario and how we’d solve it.

Say you have a Node.js cluster, and the master process plans to PUSH a bunch of jobs to a pool of worker processes. Before the master can start pushing, the workers need a way to signal back to the master that they’re ready to start pulling jobs. They also need a way to communicate the results of the jobs that they’ll eventually complete.

The following figure shows this scenario.

images/push-cluster.png

As in previous diagrams, rectangles are Node.js processes, ovals are resources, and heavy arrows point in the direction the connection is established. The job, ready, and result messages are shown as arrow boxes pointing in the direction they are sent.

In the top half of the figure, we have the main communication channel—the master’s PUSH socket hooked up to the workers’ PULL sockets. This is how jobs will be sent to workers.

In the bottom half of the figure, we have a backchannel. This time the master has a PULL socket connected to each worker’s PUSH sockets. The workers can PUSH messages, such as their readiness to work or job results, back to the master.

The master process is the stable part of the architecture, so it binds while the workers connect. Since all of the processes are local to the same machine, it makes sense to use IPC for the transport.

The bonus challenge in Bidirectional Messaging, will ask you to implement this PUSH/PULL cluster in Node.

The Limited-Resource Problem

The other common pitfall is the limited-resource problem. Node.js is at the mercy of the operating system with respect to the number of resources it can access at the same time. In Unix-speak, these resources are called file descriptors.

Whenever your Node.js program opens a file or a TCP connection, it uses one of its available file descriptors. When there are none left, Node.js will start failing to connect to resources when asked. This is a common problem for Node.js developers.

Strictly speaking, this problem isn’t exclusive to the PUSH/PULL scenario, but it’s very likely to happen there, and here’s why: since Node.js is asynchronous, the puller process can start working on many jobs simultaneously. Every time a message event comes in, the Node.js process invokes the handler and starts working on the job. If these jobs require accessing system resources, then you’re liable to exhaust the pool of available file descriptors. Then jobs will quickly start failing.

If your application starts to throw EMFILE or ECONNRESET errors under load, it means you’ve exhausted the pool of file descriptors.

A full discussion of how to manage the number of file descriptors available to your processes is outside the scope of this book, since it’s mainly influenced by the operating system. But you can generally work around these limitations in a couple of ways.

One way is to keep a counter that tracks how many tasks your Node.js process is actively engaged in. The counter starts at zero, and when you get a new task you increment the counter. When a task finishes, you decrement the counter. Then, when a task pushes the counter over some threshold, pause listening for new tasks (stop pulling), then resume when one finishes.

Another way is to offload the handling of this problem to an off-the-shelf module. The graceful-fs module is a drop-in replacement for Node.js’s built-in fs module.[33] Rather than choking when there are no descriptors left, graceful-fs will queue outstanding file operations until there are descriptors available.

Now it’s time to wrap up this chapter so we can move on to working with data from the wild.