Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
MongoDB, Express, Angular, and Node.js Fundamentals

You're reading from   MongoDB, Express, Angular, and Node.js Fundamentals Become a MEAN master and rule the world of web applications

Arrow left icon
Product type Paperback
Published in Mar 2019
Publisher
ISBN-13 9781789808735
Length 362 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Paul Oluyege Paul Oluyege
Author Profile Icon Paul Oluyege
Paul Oluyege
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

MongoDB, Express, Angular, and Node.js Fundamentals
Preface
1. Introduction to the MEAN Stack FREE CHAPTER 2. Developing RESTful APIs to Perform CRUD Operations 3. Beginning Frontend Development with Angular CLI 4. The MEAN Stack Security 5. Angular Declarables, Bootstrapping, and Modularity 6. Testing and Optimizing Angular Applications Appendix

Understanding Callbacks, Event Loops, and EventEmitters in Node


Callback

Node is built to be asynchronous in everything that it does. In this view, a callback is an asynchronous equivalent of a function that is called after a given task is completed. Alternatively, it can be defined as a function that is passed into another function so that the latter can call it on the completion of a given task. It allows other programs to keep running, thereby preventing blocking.

Let's consider a JavaScript global function, setTimeout(), as implemented in the following snippet:

setTimeout(function () {
console.log("1…2…3…4…5 secs later.");
}, 5000);

setTimeout() accepts a callback function and a delay in milliseconds as first and second arguments, respectively. The callback function is fired after 5,000 milliseconds has elapsed, thereby printing "1…2…3…4…5 secs later." to the console.

An interesting thing is that the preceding code can be rewritten and simplified, as shown here:

var callback = function () { console.log("1…2…3…4…5 secs later."); }; setTimeout(callback, 5000)

Another example of callbacks can be seen in filesystem operations. readFile (asynchronous) and readFileSync (synchronous) are two unique API functions in the Node library that can be used to read a file.

An example of synchronous reading is as follows:

var Filedata = fs.readFileSync('fileText.txt'); console.log(FileData);

The readFileSync() method reads a file synchronously (all of the content is read at the same time). It takes in the file path (this could be in the form of a string, URL, buffer, or integer) and an optional parameter (either an encoder, which could be a string, null, or a flag in the form of a string) as an argument. In the case of the preceding snippet, the synchronous filesystem function takes in a file (fileText.txt) from the directory and the next line prints the contents of the file as a buffer. Note that if the encoding option is specified, then this function returns a string. Otherwise, it returns a buffer, just as we've seen here.

An example of asynchronous reading is as follows:

var callback = function (err, FileData) {
if (err) return console.error(err);
console.log(FileData);
};
fs.readFile('fileText.txt', callback);

In the preceding snippet, the readFile() method asynchronously reads the entire contents of a file (read serially until all its content is entirely read). It takes in the file path (this could be in the form of a string, URL, buffer, or integer), an optional parameter (either an encoder, which could be a string or null, or a flag in the form of a string), and a callback as arguments. It can be seen from the first line that the callback function accepts two arguments: err and data (FileData). The err argument is passed first because, as API calls are made, it becomes difficult to track an error, thus, it's best to check whether err has a value before you do anything else. If so, stop the execution of the callback and log the error. This is known as error-first callback.

In addition, if callbacks are not used (as seen in the previous example on synchronous reading) when dealing with a large file, you will be using massive amounts of memory, and this leads to a delay before the transfer of data begins (network latency). To summarize, from the use of the two filesystem functions, that is, the readFileSync(), which works synchronously, and the readFile(), which works asynchronously, we can deduce that the latter is safer than the former.

Also, in a situation where a callback is heavily nested (multiple asynchronous operations are serially executed), callback hell (also known as the pyramid of doom) may occur, when the code becomes unreadable and maintenance becomes difficult. Callback hell can be avoided by breaking callbacks into modular units (modularization) by using a generator with promises, implementing async/await, and by employing a control flow library.

Note

For more information on callback hell, refer to this link: http://callbackhell.com/.

Event Loops

An event loop is an efficient mechanism in Node that enables single-threaded execution. Everything that happens in Node is a reaction to an event. So, we can say that Node is an event-based platform, and that the event loop ensures that Node keeps running. Node uses event loops similar to a FIFO (first-in first-out) queue to arrange the tasks it has to do in its free time.

Let's assume that we have three programs running simultaneously. Given that each program is independent of another at any given time, they won't have to wait for the other before the output of each program is printed on the console. The following diagram shows the event loop cycle:

Figure 1.5: Event loop cycle

The following diagram shows the event queue and the call stack operations:

Figure 1.6: Event queue and call stack

To understand the concept of an event loop, we will consider the following implementation of an event loop in a program to calculate a perimeter:

const perimeter = function(a,b){
return 2*(a+b);
}
constshowPerimeter = function(){
console.log(perimeter(2,2));
}
const start = function(){
console.log('Initailizing.....');
console.log('Getting Started.....');
setTimeout(showPerimeter,10000);
console.log('Ending program.....');
console.log('Exiting.....');
}
start();

The operation sequence of the preceding snippet is listed here:

  1. start() is pushed to memory, which is known as a call stack.

  2. console.log ('Initializing …..') is pushed and popped on execution.

  3. console.log ('Getting Started …..') is pushed and popped on execution.

  4. setTimeout(showPerimiter,10000) is pushed into the stack, which is where a timer is created by the API, and the program does not wait for the callback.

  5. console.log ('Ending program…') is pushed and popped on execution.

    If 10 seconds elapse, the showPerimiter(2,2) callback function will be sent to the event queue to wait until the stack is empty.

  6. console.log ('Exiting program…') is pushed and popped on execution.

  7. Executed.start() is also taken out of the stack.

  8. The showPerimiter(2,2) function is moved to the stack and is executed.

  9. Finally, 8 is printed to the command line, which is the perimeter.

Now, we are able to understand the concept of the event loop in Node. Next, we will explore the EventEmitter, which is one of the most important classes in Node.

EventEmitter

Callbacks are emitted and bound to an event by using a consistent interface, which is provided by a class known as EventEmitter. In real life, EventEmitter can be likened to anything that triggers an event for anyone to listen to.

EventEmitter implementation involves the following steps:

  1. Importing and loading the event modules by invoking the "require" directive.

  2. Creating the emitter class that extends the loaded event module.

  3. Creating an instance of the emitter class.

  4. Adding a listener to the instance.

  5. Triggering the event.

EventEmitter Implementation

You can implement the EventEmitter by creating an EventEmitter instance using the following code:

var eEmitter = require('events'); // events module from node
class emitter extends eEmitter {} // EventEmitter class extended
var myeEmitter = new emitter(); // EventEmitter instance 

The AddListener Method

EventEmitter makes it possible for you to add listeners to any random event. For flexibility, multiple callbacks can be added to a single event. Either addListener(event, listener) or on(event, listener) can be used to add a listener because they perform similar functions. You can use the following code block to add listeners:

var emitter = new MyClass();
emitter.on('event', function(arg1) { … });

You can also use the following code block to add listeners:

emitter.EventEmitter.addListener(event, function(arg1) { … });

Trigger Events

To trigger an event, you can use emit(event, arg1), as can be seen here:

EventEmitter.emit(event, arg1, arg2……)

The .emit function takes an unlimited number of arguments and passes them on to the callback(s) associated with the event.

By putting all of this code together, we have the following snippet:

var eEmitter = require('events');
class emitter extends eEmitter { }
var myEemitter = new emitter();
myEemitter.on('event', () => {
    console.log('Hey, an event just occurred!');
});
myEemitter.emit('event'); 

Removing Listeners

We can also remove a listener from an event by using the removeListener(event, listener) or removeAllListeners(event) functions. This can be done using the following code:

EventEmitter. removeAllListeners (event, arg1, arg2……)

Alternatively, you can use the following code:

EventEmitter. removeListener(event, listener) 

EventEmitter works synchronously; therefore, listeners are called in the order in which they are registered to ensure proper sequencing of events. This also helps avoid race conditions or logic errors. The setImmediate() or process.nextTick() methods make it possible for a listener to switch to an asynchronous mode.

For example, let's say we wanted an output such as "Mr. Pick Piper". We could use the following code:

console.log ("Mr.");console.log("Pick");console.log("Piper");

However, if we want an output such as "Mr. Piper Pick" using the preceding snippet, then we would introduce an event sequencing function, setImmediate(), to help the listener to switch to an asynchronous mode of operation, such as in the following code:

console.log ("Mr."); console.log("Pick");
setImmediate(function(){console.log("Piper");});

The output of the preceding function is exactly as expected:

Figure 1.7: Output using the sequencing function

Note

Callbacks, event loops, and event emitters are important concepts in Node.js. However, a more detailed description is beyond the scope of this book. For more information on these topics, please refer to this link: https://nodejs.org/.

Some Other Features of EventEmmitter

eventEmitter.once(): This can be used to add a callback that is expected to just trigger once, even when an event occurs repeatedly. It is very important to keep the number of listeners to a minimum (EventEmmitter expects the setMaxListeners method to be called). If more than a maximum of 10 listeners are added to an event, a warning will be flagged.

myEmitter.emit('error', new Error('whoops!'): This emits the typical action for an error event when errors occur within an EventEmmitter instance.

You have been reading a chapter from
MongoDB, Express, Angular, and Node.js Fundamentals
Published in: Mar 2019
Publisher:
ISBN-13: 9781789808735
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime