A discussion about Node Modules is incomplete
without talking about Callbacks and Error handling.
What are callbacks and why are they essential for
supporting asynchronous computation that is very
useful when we write Node.js applications?
How is error handling done in Node applications?
Let's talk briefly about these in this lecture.
Before we proceed on to talk about Node Modules and Callbacks,
we need to understand two salient features about the JavaScript language itself.
First and foremost, JavaScript supports what is called as first-class functions.
What do we mean by first-class functions?
Is that a function can be treated just like any other variable.
And hence, functions can be passed around as
parameters inside function calls to other functions.
And that essentially allows us to send in functions as callback functions that can be
called from another Node module in order to get some work accomplished.
Then look at how this is very useful in supporting callbacks in Node.js.
The second aspect about JavaScript is the support for Closures.
What we mean by Closures?
Especially, if you are familiar with functional programming languages,
you understand how closure works.
A function defined inside another function
automatically gets access to the variables that are declared in the outer function.
So even if the outer function is completed execution,
when the inner function executes later the inner function will still have access to
the values of the variables within that outer function.
And this is again,
very effectively used when we use Callbacks in Node applications.
If you are used to the standard way of writing applications,
then you are familiar with synchronous computation where you
specify a computation as a set of steps to be done one after another.
Now, if you organize your computation as follows,
as shown on the left side of the slide here,
we have computation one then followed by a long running computation,
followed by computation two, and computation three.
So in this arrangement,
let's assume that computation two is dependent upon
the long running computation completing its work,
then it makes sense for computation two to wait until
the long running computation is completed before it gets executed.
Now, we might have another piece of work to
be done which is defined by computation three.
Computation three may be in noway dependent upon computation two,
or the long running computation to complete before it is executed.
So in this case,
if we do synchronous execution of this computation,
then computation three will get stuck,
even though it is no way dependent upon the completion of either computation two,
or the long running computation.
So thereby, the amount of work to be done by computation three,
will be delayed because of no reason of it's own, right?
Instead is delayed because of something that precedes it in the sequence of computation.
Now, asynchronous computation, we can rearrange this work
in such a way that computation one, when it completes,
can spawn off those long running computation to be done behind the scenes independently,
and since computation two is dependent upon the long running computation to finish,
then computation two can be executed when the long running computation is finished.
Now, this frees up computation three to continue on,
right after computation one finishes.
So this way of arranging,
ensures that computation three will finish much faster than
being stuck behind the long running computation, and computation two.
So this approach of rearranging your work is very useful especially,
when you have a long running computation especially,
I/O bound computation to be done.
So in this case, you can spawn off the I/O bound computation,
and when that completes,
then whatever needs to be done thereafter,
can be sent to it as a Callback.
So computation two will be turned into
a Callback and then given off to the long running computation.
So when that completes its work,
then it'll call back the function that is enclosed inside the callback.
So this is leveraged very effectively by Node.js
in rearranging the computation within our Node applications.
Now, this is very,
very useful when we look at the way Node.js itself runs.
So as we realize,
Node.js is organized into a single threaded event loop.
This single threaded event loop basically,
picks up requests as they come in and execute it one after another.
Whenever it needs to spawn off an I/O request,
the I/O request will be spawned off,
and any work that needs to be done after the I/O request is completed,
will be enclosed inside a Callback.
So when the I/O request completes,
then it'll put the Callback into the request queue,
and the Callback will be handled thereafter,
by the event loop.
So the event loop is a continuously running loop
which basically picks up requests from the request queue,
and then services them one at a time.
So when you think back about it,
you realize that Node.js is a single threaded,
but at the same time,
it is able to achieve much faster rate of completion of work,
simply because of the judicious use of callbacks,
and the asynchronous execution of I/O requests,
like for file accesses or database,
or long running processing that can be done independently behind the scenes.
Now, the way the Node.js event loop handles all this,
is that the event loop is arranged in a sequence of phases.
So as you see in the diagram shown here,
the phases include timer handling,
I/O callback handling, then you have idle, prepare,
then the poll where the incoming requests for connections or data are handled,
and then the check phase,
and then finally, the close callbacks phase.
Now, some details about what is done in each of these phases is listed to the right side.
In the timer phase,
the event loop handles anything that is triggered by the
setTimeout() function in JavaScript.
The I/O callbacks are executed,
almost all the callbacks that come back to be
executed after an I/O request will be handled by the I/O callbacks queue.
So each of these phases maintains its own separate queue,
and the Node.js event loop picks up requests from each of these queues, and handles them.
The, idle, prepare, is meant for internal use by Node.js.
The poll is where it retrieves new I/O events to be handled and perhaps,
the requests coming in from outside.
The close callback phase handles any socket closures that need to be handled, and so on.
Now, we don't need to worry too much about
all these details if we need to really write a Node.js application.
When required, we will look at some of these details as and when
it is essential for understanding how our Node application works.
But for the moment, this gives you a big picture view
of how the Node.js event loop is handled.
Sufficed to say that this is sufficient enough information for us to
understand the Node applications and how they work for the moment.
If you want to know more details,
then I have given you a link to a detailed description in the Node.js
documentation which explains about the event loop in more detail.
With this quick understanding of Node,
Callbacks, and Error handling,
we will move on to the exercise,
where we will look at how a Node application can written with Callbacks,
and how errors can be handled inside a Node application.
We will see repeated use of this pattern in subsequent exercises,
and subsequent lessons specifically,
when we look at Express,
and how we build server side applications using Express
in more detail in the later part of this course.