Instructor: Let's now take a minute to learn about
the asynchronous nature of Node.js,
which includes absolutely fundamental topics,
like synchronous, asynchronous, blocking,
and non-blocking code.
And all of this will be really important
in order to understand everything that's coming up
throughout this section.
So this piece of code that we wrote in the last lecture,
to read a file and then, save it's content into a variable,
was in a so-called synchronous way,
which simply means that each statement
is basically processed one after another, line by line.
In this example,
first, the file system module is required,
then, the file is read,
and then, we log the result to the console.
So you see that each line of code
basically waits for the result of the previous line.
Now, this can become a problem,
especially with slow operations,
because each line blocks the execution
of the rest of the code.
And so, we say that synchronous code
is also called blocking code because, again,
a certain operation can only be executed
after the one before has finished.
And because of the way Node.js was designed,
this turns into a huge problem, as we'll see in detail
in the next slide.
So the solution to this problem in Node
is to use asynchronous, non-blocking code.
So in asynchronous code, we upload heavy work
to basically be worked on in the background.
And then, once that work is done,
a callback function that we register before
is called to handle the result.
And during all that time,
the rest of the code can still be executing
without being blocked by the heavy task,
which is now running in the background.
So what this means is that we can effectively defer
or reaction into the future
in order to make the code non-blocking
and this is, of course, much better.
Makes sense?
So, in this example, we use the asynchronous
readFile function, which accepts a callback function.
This will start reading the file in the background
and then, immediately move on to the next statement,
printing to the console the string-reading file.
So, again, you see, we are not blocking the execution here.
Then, when the file is finally completely read,
the callback function will be called,
and so, the data that was read
will then be printed to the console.
So that's quite different
from the synchronous version, isn't it?
Now, the question here is,
why does is actually have to be this way?
What's the problem with blocking code execution in Node.js?
Or, in other words, why do we actually use callback
so many times in Node.js?
Well, let's find out.
And in order to understand these questions,
the first thing that we need to understand
is the fact that a Node.js process,
which is where our application is running,
there's only one single thread.
And the thread is just like a set of instructions
that is run in the computer's CPU.
So basically, the thread is where our code
is actually executed in a machine's processor.
So, remember, Node.js is basically single-threaded
and so, for each application, there's only one thread.
That's just the way Node.js was designed.
Now, what that means is that all the users
accessing your application are all using the same thread,
so, basically, accessing the same thread.
And so, whenever they're interacting with the application,
the code that is run for each user
will be executed all in the same thread at the same place
in the computer running the application.
And that is true no matter if you have
five users, like in this diagram, or 5,000 or 5 million.
All right?
Now, what this also means is that when one user
locks the single thread with synchronous code,
like we just saw before,
then all other users will have to wait
for that execution to finish.
And that might not be a huge problem
if you have like five users,
but it definitely will for thousands or even millions
of users at the same time.
So, imagine there's a user accessing your application
and there's a huge synchronous file read in your code
that will take like one second to load.
This will mean, of course, that for that one second,
all other users will have to wait
because the entire execution is blocked for that one second.
So if those other users want to do some simple tasks,
like logging into your application
or just requesting some data, they won't be able to do so.
They will have to wait until the file is finished reading.
Only when that happens they will finally be able to perform
the simpler tasks, one after another.
Now, please note, that this is a very oversimplified version
of what really happens behind the scenes of Node.js,
which is why we will come back to all of this
in the next section and get an even deeper understanding
of how Node.js handles asynchronous code under the hood.
But at this point,
this is enough for you to understand the concept.
It's better to go step-by-step here
and not make it too confusing
right from the beginning, okay?
Anyway, this is how the situation
would play out with synchronous blocking code,
which is obviously a terrible experience for your users.
And so, it's really your job as a developer
to avoid these kinds of situations
by using asynchronous code.
So, for the same situation, we should, of course,
use the asynchronous file read function,
which instead of blocking the single thread,
does the heavy work in the background,
where it basically stays until it's finished
reading the data from the file.
Of course, we then also register a callback function
to be called once the data is available.
And in this scenario, all the other users
can then perform their tasks in a single thread,
one after another, while the file is still being read
in the background.
Now, once the data is read, our callback function will,
of course, get called to be executed
in the main single thread in order to process the read data.
And that's it.
That's an overview of how Node.js
handles asynchronous behavior in order to implement
the non-blocking I/O model that we talked about
in the intro lecture, all right?
And I/O simply stands for input-output,
which is basically stuff like accessing the file system
and handling network requests.
This is actually the whole reason why Node.js
is completely designed around callbacks,
as you will see throughout the course.
In other programming languages, like PHP,
it works very differently because you get, basically,
one new thread for each new user,
which is a completely different paradigm
and really works completely different.
But the creator of Node.js found this model
to be the best solution for building highly performant
and scalable web applications.
Now, just as a final note here, it's important to know that,
when we use callbacks in our code,
that doesn't automatically make it asynchronous, all right?
So, passing functions around into other functions
is quite common in JavaScript,
but of course, again, that doesn't make them
asynchronous automatically, okay?
It only works this way for some functions in the Node API,
such as the readFile function
and many, many more, as people explore in the future.
And now, just to finish,
since we're talking about asynchronous code here,
just one last note about callback functions.
So, this callback model that we just discussed,
where one function is called
once the one before has finished it's work,
can quickly lead to some hard to read and unmanageable code.
Just take this example where the second file read
depends on the first one,
then, the third file read depends on the second one,
and finally, we want to use the final data
to write a file as a result.
That looks quite confusing, right?
I mean, it's gonna work just fine,
but it's just hard to reason about
and that is just with four levels deep.
Imagine you had like 10 or 20 levels,
which is actually not that uncommon.
Anyway, this is what we call the callback hell.
It's such a common problem,
that it already got its own name.
And do you notice this triangular shape here?
That's a very clear sign that you're in callback hell.
Now, how do we actually escape callback hell?
Well, we can use more advanced tools for handling
asynchronous code, like ES6 promises
or even better, ES8 async/await.
Now, the model that we just talked about
will still be the same.
We just have more elegant ways
of dealing with the code itself and writing it.
And there is a whole optional section
of it later in the course on promises and also, async/await,
so in case you're not familiar with them.
But for now, we will keep using callbacks
because that is what Node originally used
and was designed around.
And now, with that being said,
let's move on and use this asynchronous model
in practice for the first time.