Now, as a last but very important concept
in this course section,
we'll dive into asynchronous code.
And for that, I'll come back to that readFile example
we had before.
I'll copy the code from errors.js
and add a new async.js file
into which I'll paste that code.
And I'll get rid of the try catch part here
and write it like that instead.
And I will add a data.txt file now actually
where I just say this works,
data from the text file!
So just some basic text that's stored in this text file.
And in async.js, I'll try to read that TXT file
instead of a JSON file.
So I changed that extension.
We also don't need to create a path to that file
since it sits very much next to the code file
where we execute this line of code
and in that case, we can just refer the TXT file
by its name like that.
Now, I'll also get rid of that line here.
And if we now save everything,
and I then execute async.js,
we don't get an error anymore.
Instead, we get this strange buffer thing
but that's just how the file data is actually read in.
This will go away if we, for example,
call toString on FileData to force a conversion
to a human-readable string.
It's a built-in method you can call on basically all objects
and values in JavaScript to force a conversion to a string.
And if I force that with toString here,
and I then execute asynx.js again,
then I see the actual text
that was stored in the file.
Now, of course, that's still nothing new in the end.
Before, we didn't have to do this toString thing
because we used JSON.parse to parse the content
of a JSON file.
Now we have to do it because that's not a JSON file,
and we're not using JSON.parse
but in the end, we're still reading in a file
just as we did it before in the course.
But let's think about that file reading operation here
for a second.
Is there anything about that operation
that could be special?
Well, actually the name of this method
is already a bit strange.
Why is it readFileSync?
Because here we actually force NodeJS
through the file system package
to synchronously process and read that file.
And the opposite of that would be asynchronous processing.
But what's that all about?
Well, the special thing about reading a file
is that this is a process,
which could take quite a while.
We've all been there where we wanted
to open a large PDF or Excel or image file,
and it took a couple of seconds until that file opened
on our system.
Totally outside of NodeJS and coding,
just by double clicking on a file, right?
We all experienced that opening files can take a while.
The bigger a file is, the longer it takes.
And NodeJS code is no exception.
If you open a file programmatically
to read its content and use it in your program,
that can take a while.
Of course, data.txt is a simple, small file,
which will be opened quickly.
But in web development,
and development in general,
it's also quite common
that you work with bigger files with more content.
Now, if you open such a large file
with readFileSync, what happens is that code execution
will be blocked until that file was read in
and parsed by NodeJS.
That means the lines thereafter
will not execute before this line is done.
Now, here that's actually helpful
because outputting the file content
only makes sense after the file was read.
So we wanna wait for this to finish.
But you might have other operations
that you might wanna start at the same time
as you do read that file.
Again, let's say outputting hi there
is something with which we don't want to wait.
Now, of course, we could just put that code
in front of reading the file synchronously
but what if that is also a long-taking operation?
A console.log clearly isn't
but what if we would be reading a database value here?
That could also take quite a while
because we have to establish the connection
to the database, send a request to the database,
query the data and use the data.
So what I'm saying
is that we could have multiple long-taking operations
that are started in the same part of your program.
So therefore, it would be nice
if we could output hi there
and execute line nine before the lines six and eight,
which depend on each other are finished.
And that's where asynchronous code execution
comes into play.
For reading a file,
we actually have this synchronous version
but we also have the asynchronous version
without sync in the name.
Now the code will execute a bit differently.
If we save it like that,
and we then execute async.js, we get an error.
Now I get a invalid callback error here.
And what's that?
Well, readFile now actually requires a second argument,
a second parameter value
that should actually be a function.
Either a function which you defined in advance,
which name you now use
or an anonymous function,
which you define just in time
as we did it before
when we worked with Express and its route handlers.
This is a function, this function here,
that will be executed by readFile
once processing that file is done.
So it's a little bit like adding an event listener
and waiting for clicks.
What we did in the browser earlier in the course.
There we also defined a function
that should be executed once a click occurs.
Now we're defining a function,
in this case, an anonymous function
that should be executed once parsing that file finished.
Now, for the moment,
I'll just console.log file parsing done here,
and then again save everything.
If we now execute this program again,
I get another error.
Cannot read property toString of undefined.
Now, this error is now coming from line 10,
as it also tells you here.
It tells you here that it's line 10
and it even shows you the code line
and the place in code that's causing that issue.
It's this line of code
and specifically the fact
that we're executing toString on fileData.
The problem is that fileData now all
of a sudden is undefined.
That's what it's telling us here.
And you learned that undefined is a built-in value type,
which is basically used if no value exists
in a variable or constant.
Now, why is fileData now undefined
when we did actually parse that file?
Because as I explained,
readFile now starts this file parsing and reading
in a asynchronous way,
which simply means it tells NodeJS to start reading the file
and then it immediately continues
with the next line of code.
In this case, line 10.
It does not wait until reading that file is done.
And even though reading that file
will be super quick in this case,
since it's a small file,
it will still take one or two milliseconds
or something like that.
And therefore, executing this line of code,
line 10, will definitely be quicker
than this file parsing,
even if it is just one millisecond
but the execution of the next line is pretty much instant.
Faster than one millisecond.
Therefore, in line 10, fileData is undefined
because we're not done with reading the file.
And indeed, readFile, when executed like this
in the asynchronous version works totally different
than readFileSync.
It does not return the fileData,
so we can't store the fileData here
in a fileData variable or constant.
Instead, the parsed fileData will only be made available
in this function, a so-called callback function
since it will be called by readFile for us
once it's done with its operation.
Hence, this function here will actually receive values,
parameters passed in automatically by readFile.
To be precise, it will receive an error object,
which will be undefined if no error occurred
but which will hold error information
if reading the file failed.
And it will hold the fileData
if parsing and reading the file did succeed eventually.
Hence, if we then wanna work with that file,
we have to do that from inside this function,
which we pass as a second parameter value to readFile
because only inside of that function,
the fileData will be available.
Not in the code after this code block
because this now starts this asynchronous operation
and the code lines after this asynchronous operation
are executed before that asynchronous operation finished.
And that would allow us to execute and start multiple,
potentially long-taking asynchronous tasks
at the same time without blocking script execution.
So therefore, now with that adjustment made,
if I now execute async.js again,
you see, we first of all see hi there!
That's our first output there
even though it's the last line of code in this function
but it's the first output we get here
because as I said, code execution does directly move on
to line 11 after reading the file was started,
and it does not wait for the file reading to finish.
That's why the output from inside this callback function,
which is only executed once file reading finished appears
after hi there
because it was executed asynchronously.
And it's hopefully clear what the idea here is
and how that asynchronous code execution works.
And the benefit is simply that you can start one
or multiple long-taking operations
without blocking other code execution,
which can speed up your programs by a lot
if you have multiple long-taking operations.
Therefore, it is considered a best practice
to use async functions like this for such tasks.
Now, how do you know which tasks are long-taking
and which tasks aren't?
Well, commonsense already gets you pretty far,
reading a file, talking to a database,
sending some HTTP request,
these are long-taking tasks.
And we'll see examples, plenty of examples
throughout this course.
And in addition, you can always look up the documentation
of some method from a third-party package
or a built-in package to see how it works.
And if you look up the readFile documentation
in the NodeJS documentation,
then you'll learn that this takes a couple of parameters,
and whenever you see a callback parameter,
then this is a synchronous task
because it wants you to specify a function
that should be executed at some point in the future
once the operation finished.
Here in this case, it even tells us
that it asynchronously reads the entire contents
of a file.
So here in this case, we even get an explicit confirmation
that this is an asynchronous task.