Now that we know the overview of Node.js from the last chapter, it’s time to get into some core programming concepts of the language that changed it all – JavaScript. JavaScript is deceptive in that its lightweight structure will make you feel that you’re indeed done with the whole language, because you have an app running and returning a response. If you dive in deeper – which is always a good thing in programming – JavaScript can offer a whole new world, where thorough concepts can make or break an app and its scalability. Let’s walk you through. We’ll just be explaining the most difficult parts of JavaScript pertaining to Node.js – for the most commonly understood sections, we’ll leave a trail of further reading and online recommendations.
Understanding Asynchronous Programming in General
Somewhere down the line, in data-intensive apps, you’ll realize you want to use resources in the most efficient manner, without blocking a handle to those resources. Now, when do such use cases come along? A massive number of users on the system at one time, intensive I/O operations, and so on. So what do we do? We get the privilege of initiating an operation and it getting handled when the resource is free. Compared to a multi-threaded environment, this brings in a much cleaner, single-threaded, non-blocking way of programming. Again, JavaScript in itself is not asynchronous; it supports asynchronous programming.
For those of us who’ve been novice developers in client servicing, we have never had the time to delve into the skin of things – it’s a little obscure region to talk about what blocking resources means. Well, time for some real-world examples again. Restaurants, anyone?
Many browser functions are implemented asynchronously. Looking at JavaScript info (https://javascript.info/callbacks) would make our point clearer.
Now, JavaScript is a single-threaded language, and to get into blocking calls is very easy if not written properly. For a long time, asynchronous programming was served well by callbacks. The evolution of JavaScript saw promises and generators. We discuss callbacks next. We wish to point out to developers that just like JavaScript, callbacks are not asynchronous themselves. Callbacks can be used asynchronously. Whenever you revisit the topic, look for synchronous callbacks and asynchronous callbacks separately.
Understanding Callbacks
Callbacks are an example of first-class functions in JavaScript, where a function is passed to a function. To understand how they tie up with asynchronous programming, consider the difference between loading a file, reading a file, and some code below it (you don’t want that to wait) – what you do here is keep the loading and reading asynchronous, and the rest of the code does not depend on it.
An Ajax Example with a Callback Function
The program doesn’t wait to return the file. It prints Me First first, as shown in the following screenshot; and then when the file is loaded, it prints the response. Note that this is nonsequential, according to our code. The third screen shows the actual output that displays after some time.
Code Without Callbacks
Output Without Callbacks
What happened? The console dumps out the Me First, Me Second quickly, and then when the response is ready and is successful, it’s served, still maintaining sequential order. You could also add a timer to the code and see how many seconds elapse for what task – we leave that task to you.
Callback syntax can be confusing for non-JavaScript developers, and the way I keep track of it is to see that the callback receives its arguments – if any. For multiple callbacks, nested versions can become messy. This brings us to the concept of promises.
What’s in a Promise?
Coming back to the restaurant analogy, the manager or the cashier hands over an order number – which is a promise to deliver. What does it say? Collect your order when it is ready. Again, execution is not delayed after the first order is complete – we just say we deliver the order when it’s ready.
Again, we are achieving asynchronous results; and yes, the same can be written in callback language as well.
resolve (value) – If the job finished successfully, with result value.
reject (error) – If an error occurred. Error is the error object.
The state of the promise can be one of “pending,” “fulfilled,” or “rejected.” A promise returns the state and value, though state and value are not directly accessible.
When you use .then(), you’re telling your code to proceed if the resolution of the promise is successful. Through a series of .then() functions, called chaining of promises, we use the return value of one into the parameter value of another.
Demonstrating Promises (script.js)
index.html
The fetch method returns a promise, the value of which is the response object if the promise is resolved.
If the promise is resolved, it returns a JSON representation of the response object. This is passed further down the line, through another successive then() method call. This way, we ensure that the JSON representation is passed only when it is completely resolved. That JSON representation is stringified and dumped into a div of your choice.
The preceding example is a live working example, and you can easily test it in a fiddle. It can be very easily modeled to a restaurant analogy as well. Your loadDoc() function would be a loadOrder() function. You could then serve the response as and when it is prepared (ready – status resolved, marked by your API). The idea is the same here – we make a promise to deliver when ready.
A detailed study of promises is out of the scope of this book. Refer to our recommendations in the “Further Reading” section at the end of the chapter. Especially focus on promisification if you want to understand how everything you do in callbacks can be converted to promise code. That’s for those who want to take their time on JavaScript and then reach Node.js.
async/await
One reason why we’re giving you a bite of JavaScript is because these four concepts of asynchronous programming are quite often used in Node.js, especially in hapi.js. You’ll see the await keyword in the server.js file, for instance, and to understand what’s going on with it, let’s understand how async and await help.
Code for Demonstrating async/await
Many users prefer this because it’s very similar to how we say things in other languages.
The best way to understand async behavior in the preceding piece of code is to remove await from line 5. Try saying const json = response.json();
You’ll get an empty brace {} on the screen. This shows us that if the response hasn’t loaded and we do not use the await keyword, it will not wait for the response to load and the control will move on.
Event Loop
We take up the event loop when we are discussing the entry point of a Node.js application in the chapters to come. Till then, know a very simple definition: here’s an endless loop, when JavaScript engine waits for tasks, executes them, and then sleeps waiting for more tasks.
Tying JavaScript with Node.js
In the beginning, before deep-diving into code, you must know that Node uses the Event-Driven Architecture: it has an event loop for orchestration and a Worker Pool for expensive tasks (includes I/O for which an operating system does not provide a non-blocking version, as well as particularly CPU-intensive tasks). The event loop will also fulfill the non-blocking asynchronous requests made by callbacks. While the event loop does not maintain a queue on its own, the Worker Pool does. When Node.js starts, it initializes the event loop, processes the provided input script (making async calls, scheduling timers), and then begins processing the loop. The loop in itself does not maintain a queue, but for a single-threaded language, it helps a lot by checking queues to see if it can take an input from a queue or a phase and process it.
A Worker pops a task from this queue and works on it, and when finished the Worker raises an “At least one task is finished” event for the event loop.
All in all, the modules of Node.js are very well equipped to handle asynchronous programming – which makes it a natural choice for mobile application APIs. You need to serve a lot of data in mobile applications, so quick-to-write and intricate tailoring becomes a good combination when serving data for scalable applications.
Hapi.js
hapi offers a rich ecosystem. The plugins available have almost always covered every application design need.
The guarantee provided by hapi is very strong – for instance, the order in which components are configured is not a problem.
Plugins can safely rely on other plugins, including the order in which they must be executed.
Caches, plugins, decorators, and server methods are all protected and cannot be implicitly overridden – so you can say goodbye to the middleware hell. The fact that the framework itself has plugins makes the abstraction layer very flexible.
Summary
This chapter showed you some key concepts of JavaScript that might come in handy when understanding any framework in Node.js, because Node.js itself was adopted for modules of asynchronous programming. As we go forward, becoming clear with concepts and syntax of promises, async/Ajax will help, and remembering the callback pattern as the first approach to solve problems asynchronously will help in understanding why we write the code the way we do. This was more of a JavaScript primer, lest we face confusions in the keywords that might follow.
We also read about the event loop briefly and how it is used in Node.js. The event loop is what allows Node.js to perform non-blocking I/O operations – despite the fact that JavaScript is single-threaded – by offloading operations to the system kernel whenever possible.
We tied JavaScript’s asynchronous behavior to Node.js, and we saw how hapi.js is a lightweight framework to use Node.js. All in all, we have the right recipe for a quick RESTful Web Service app. As an exercise, try coding simple applications in callbacks and promises. See you in the next chapter.
Further Reading
https://javascript.info/promise-basics
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise