Event Driven Paradigm in Node.js | Yet Another Thoughts
Sometimes in our study and in work programmers can finish their tasks via programming utilizing amazingly abundant libraries, frameworks, and apis. That’s why we are sometimes being referred as coding farmers, just assembling, repeating neatly. This is definitely not true. We cannot earn as much as we already have just for those repeating work. A valuable programmer uses those tools and gets to understand what’s under the hood underlying the simple interfaces. This is more important when we do refactoring and performance optimizing and also designing interfaces or architectures.
Node.js is quite popular these years and it is famous for its event-driven paradigm and async programming styles, and however, the callback hells, too, which can be prevented by good designs and coding styles and also tools like promise. I am a newbie for node.js and I write this to motivate myself to learn the new concurrency model and programming styles.
Blocking/nonblocking and synchronous/asynchronous
Node.js is often discussed with asyncIO and nonblocking. The difference between asynchronous and nonblocking is sometimes confusing.
Blocking and nonblocking
When we say a call is blocking or nonblocking, we are saying whether the caller will be blocked to get the result. A blocking call will cause the suspending of the thread until the result is fetched via the disk IO or network request or other slow data fetching while a nonblocking call will never suspend the calling thread and return the fetching status.
Interestingly, nonblocking is not necessarily asynchronous. We take the waitress in McDonald & KFC as examples.

A waitress in McDonald gets the orders of a customer and checks the preparation status which is quite slow when being queried. When it is ready, the customer queries and ask to get the food. The customer keeps waiting and querying the status in a routine. Querying the status of the preparation is a nonblocking call, but the customer may be capable to go around a little while but not too bar because he should check the status in a period of time otherwise his girlfriend gets hungry and angry. He can also keep waiting here and querying to get his girlfriend’s mouth shut.
KFC waitress also does the same work. But the waitress herself does the preparation work and returns with the food. The customer cannot do anything but waiting. This is a blocking call.
You might think the way McDonald does is better. But both of them is nothing but synchronous operations. The customer cannot leave but waiting. So you can see, nonblocking way is not necessarily a asynchronous operation. In other words, they are different kinds of concept but related.
Synchronous and asynchronous
A synchronous operation may be slow and always return the result no matter the operation succeeds or not.
An asynchronous operation tries to schedule a slow job but get returned fast without the result. The thread can continue to do other things. The result is processed by the assigned procedures which is able to get the context of the task. Usually, when emitting a asynchronous job, the thread also registers the designed procedures with the current context bound(closure) for succeeded situations and also failed situations. When the slow job which may be disk IO or network IO is done, the scheduler schedules the registered procedures for the ready data. In a typical example, node.js uses the callback mechanism.
You and your girlfriend goes to the KFC to grab some food. But the waiting queues are all so long. Everyone is waiting to get the food and then the next customer can order. You estimate you may wait for more than half an hour to get the food and you are confused to leave.
You come to the next McDonald. McDonald has much fewer queues with much fewer waiting people. It also comes up with a new service that after ordering the food, you can tell the waitress where the food should be sent. Then you could talks and flirts sitting in the chair of MCD or goes shopping. After 10 minutes, the food is ready and get processed as you desired.
It is obvious that MCD could get a better throughout rate and earn more than KFC. KFC tries to solve the slowing food preparations problem by adding more queues and waitress. But the number of chefs are certain and get different orders at the same time and already cannot be faster. Moreover, different waitresses want to clear their staging jobs and urge the chefs to get their job done. It is clearly slowing the chefs down and he has to switch contexts to a different order many times in a short time.
The MCD has fewer waitresses or even one waitress. The customer doesn’t have to wait for the completion of the job. The day of the customer could be nicer and the MCD system schedules the job by some strategies for the chefs to do.
Event driven and node.js
Node.js is based on the JavaScript language and the high-performance V8 engine. It is based on event driven paradigm and is as simple as being seen as a single-threaded event-loop to deal with jobs in a assemble line. Internal the node.js engine, it uses libuv to provide cross-platform asynchronous IO support.
Node is always been said that “Everything is parallel except your written code”. Except the not exact word parallel(actually it’s concurrent), it makes sense. It is suitable for high concurrency jobs and making IO bounded jobs concurrent but not a lot of complex logics and computations in your code. It is single-threaded and avoids the cost of context switches for multiple threads.
Event loop
Event driven paradigm is often talked about in front-end programming, which is JavaScript’s old dominating field. UI components are created based on data models in the back and are registered by a series of listeners to deal with events user or server activates. Data can be fetched by Ajax, which is naturally asynchronous. If you use Ajax in synchronous way, it will block the single thread to wait for results.
Node.js is based on the event loop to iterate the registered event queue by a single thread. Even though we say it is single threaded, actually internally it has a thread pool. Node makes this transparent and abstracts a single threaded running environment for the programmers to be able to avoid multi-thread resource racing. The existence of the thread pool is for the kernel node bases on doesn’t support doing everything asynchronously. In those cases Node has to lock a thread for the duration of the operation so the main thread can continue to execute the event loop so it looks like being asynchronous.
If using MCD as an example, the one waitress gets the order of food and procedures to deal with the prepared food and she can turn to the next customer. She also needs to ask the appropriate chef to do the work. And continues to serve.
EventEmitter
In Node.js, you could utilize the event driven model to program just like the front-end UI event programming.
EventEmitter is in the events module of Node library. It can add listeners using “on” functions for customized event and bind callback for the event. Others use “emit” call to trigger the target event and send information to the EventEmitter object.
Again, we use the MCD example with CoffeeScript and EventEmitter. In this example, Waitress is a subclass of EventEmitter. It registers “order” and “foodReady” listeners to deal with the orders from customer and the food ready event from the chef. When customer orders some food, and tells the waitress how to deal with the food, the waitress first record the dealing method and dispatches the orders to suitable chef. The chef takes the orders asynchronously and prepare the foods one by one, which in the code is a nonblocking call (This seems like a computation-tensive job and in real impl it can create a subprocess to do it and will not slow the event loop down).
#CoffeeScriptevents = require "events"_ = require "underscore"class Waitress extends events.EventEmitter constructor: (@chefList) -> @customerToDealWithFood = {} startsToServe: -> that = @ @on "order", (customer, orders, dealWithFood) -> #record the food dealing method that.customerToDealWithFood[customer] = dealWithFood _.each orders, (order) -> chef = that.selectChef(order) chef.prepareFood order, customer, that @on "foodReady", (customer, food) -> dealWith = that.customerToDealWithFood[customer] dealWith(food) selectChef: (order) -> #select the right chefclass Chef prepareFood: (order, customer, waitress) -> #_prepareFood is a long-duration asynchronous nonblocking call food = @_prepareFood order, -> waitress.emit "foodReady", customer, food#customerchefList = [new Chef, new Chef, new Chef]waitress = new Waitress chefListwaitress.emit "order", "conndots" , ["hamburger", "chips", "cola"], (food) -> sendToHome food, "Big Street, No.4"In a traditional way, we are used to send signals to an object by method calls to trigger the action, which is synchronous. To make it asynchronous, the programmer may have to implement a signal queue and uses another thread to consume the signals, which arises the problem of data consistency problem. Adding locks to those data may slow the process down and may have hidden problems.
The point
The point is that, Node’s abstraction of event driven paradigm and single-threaded event loop makes these simple. You don’t need to consider the data consistency. For those IO bounded high-concurrency jobs, Node is a good solution for its asynchronous IO models to fully utilize the cpu time, and for its single-threaded loop to avoid data racing and cost of context switch.
If your node contains a large portion of computation and they are executed in the main thread, it will occupy two much time and slow down the whole one event loop. The other events are delayed. It is not wise to simply write down the computation-tense code in the node code and leave them there. These computations should be done in a subprocess forked by using subprocess module and wrap those into an asynchronous method call. Then, the main thread just forks a thread to do the computation and continues the event loop.
Another fallback for Node is the coding style. Often a not good design of Node codes make them full of callbacks and is hard to maintain and understand. This can be fixed by applying good coding styles and regulations. And many tools like then/promise simplify the codings of Node.js callbacks.
Usually, the callback hell also means the code is hard to debug bacause the lack of necessary tests, Node exception mechanisms.
Next
- A deeper analysis of the implementation of event loop and event emitter.
- A introduction of goroutine/chanels/CSP.