Real-Time Web with Node.js Real-Time Web with Node.js

Asynchronous File IO

Kyle Simpson
You Don't Know JS
Check out a free preview of the full Real-Time Web with Node.js course:
The "Asynchronous File IO" Lesson is part of the full, Real-Time Web with Node.js course featured in this preview video. Here's what you'd learn in this lesson:

Kyle changes the program to read the file asynchronously and passes the readFile method a callback function. This function will be called when the file stream is completed. Kyle also demonstrates how to handle errors in Node.

Get Unlimited Access Now

Transcript from the "Asynchronous File IO" Lesson

[00:00:00]
>> [MUSIC]

[00:00:03]
>> Kyle: Does anybody notice something different about this program than it was before, when we were printing out Hello World?
>> Kyle: Anybody see the difference?
>> Speaker 2: There's an extra new line there.
>> Kyle: There's an extra new line. Why is that? Anybody have a guess?
>> Speaker 3: The string [INAUDIBLE]
>> Kyle: Console.log does put a new line, but why is there an extra new line?

[00:00:25]
>> [INAUDIBLE]
>> Kyle: Cuz the file has one. Because most people know that you're supposed to put trailing new lines. You're not supposed to have a missing trailing new line in your files. So when I created test.txt, there was a trailing new line there. That's just something to be aware of.

[00:00:42] That if using console.log, and you're pulling in contents from files. It's gonna print out everything that it got, including that trailing newline. It's not going to do anything special, like stripping that off for you. It's not a big deal. We'll leave that one alone. So right now, if we go back and kind of look at where we're at.

[00:00:58] We have a wholly separate file in our file system that's acting as a module. That is the primary starting point for the path that you need to take for organizing your Node.js code. You wanna create things in terms of different modules for different tasks. Now you notice that when I discuss this, I didn't talk anything at all about delegation, or prototypes, or any of that other stuff.

[00:01:26] Those are things that JavaScript obviously has available to it. And you could take advantage of those patterns if you wanted to. But the most natural kind of easy, without any kind of friction, path here is to just simply put your code in a file and load it with the require system.

[00:01:41] Which lends itself to the module pattern a lot more so than it does to the prototype or even the delegation pattern. That even further shows you the evidence of why I said, yesterday. 95% of the time, or more, I just do things with modules. Because that's just the way you most naturally work with these things.

[00:02:01] It's possible inside of this module for you to go to all kinds of trouble to write classes, and inheritance, and all that. If that's how you feel like you wanna do it. The way we're gonna organize our code inside of Node.js is through the require module system
>> Kyle: Let's take one more step with this first file and we'll be done with 1.js.

[00:02:30] And that'll be a good, natural place for us to stop. We did say that, in general, you wanna try to avoid synchronous blocking API. So even though this is a console program, let's try to get the better practice of dealing with things asynchronously. Most of the API's that Node will provide to us provides us both the Sync form.

[00:02:48] Like we saw there, the S-Y-N-C. Or, if you take that part off, It'll do the exact same thing, except the last parameter. You'll wanna pass a callback function.
>> Kyle: So now I've got a callback that's gonna receive the contents. And what do I wanna do with that? Well, I did wanna return those back to the calling program.

[00:03:15] I didn't wanna do the console.log myself. I did wanna return them back to the calling program. But now that it's gonna do things asynchronously, this is gonna be more awkward. Cuz I can't do a return statement here and give you the stuff back synchronously. So now we get into if you follow the natural pattern of Node, the natural pattern of Node is to do things with callbacks.

[00:03:36] I'm gonna argue, as we go on throughout the rest of the day, that I think it's better to use promises. And I'm going to show you how to do that. But the natural, no-friction path in Node is just do everything with a callback. We would pass in a callback to our little say function.

[00:03:53] And, rather than making one, we could just say that callback will be called with the contents.
>> Kyle: Everybody see how we did that? Now, we go back to 1.js.
>> Kyle: And rather than receiving the contents as a return, as a synchronous return, we're gonna have it say hello.say.

[00:04:19] And we're gonna have to have a callback function that receives those contents
>> Kyle: and prints them out.
>> Kyle: If you save that code now, and run it again. Oops.
>> Kyle: I caught myself accidentally, but this is actually a really good segue to something. There is a style of callbacks in Node that is extremely common.

[00:04:55] It's not true of everything, but it's like 99 % of all callbacks in node, use what we call the error-first callback syntax. Many people call it Node-style callbacks. I think that's a bad name because this syntax is used elsewhere besides Node. The error-first callback is that the first parameter is always reserved for an error.

[00:05:18] We gotta make sure that we're passing along that error or it'll be empty when there is no error. We come here to our command line again, and we tried it again, and then it'll work.
>> Kyle: Now, what about this error thing? What happens if I type in just test, or if I just say test.txt2?

[00:05:50] Like I give it a filename that doesn't exist. [SOUND] Bad node error. It's doing that same thing as before. I didn't get any contents. Well, that's because I didn't handle the error condition. Remember, this error thing is being provided to us so that we can handle the error condition.

[00:06:06] We would probably wanna put in an if statement that says, if the error is present, then we want to handle that error. You can do console.error by the way, and that will automatically print to the standard error stream. So console.error, err, and then print out whatever you were provided.

[00:06:30] Otherwise, we're gonna assume success and print out the contents. That is the most canonical and common way that you handle callbacks in JavaScript, with the error-first style
>> Kyle: If we save that code out,
>> Kyle: and we run it again with a file that does not exist. We're gonna immediately get an error back that says, test.txt2 does not exist.

[00:07:04] It's that error interrupt. I lied and said that we were done, cuz there's one more thing I want to do to the 1.js. Because we're dealing with callbacks, we already have our asynchronicity. That means we can do asynchronicity even more directly.
>> Kyle: Inside of our Hello World, rather than calling, let me see here what I was doing.

[00:07:41]
>> Kyle: I know what it's doing. Okay. Rather than calling this fs.readFile, here. What I'm gonna do is I'm gonna immediately call. I'm gonna put my own wrapper function on it.
>> Kyle: I wanna immediately check for an error. And if there's an error, I want to immediately call the callback with that error.

[00:08:05] But if there was no error, then I want to delay the response. This is simulating something like, I got something from a file. Now I wanna go make an AJAX request, or I wanna make a database call, or something like that. So I'm going to delay sending out the response back with the contents in it by 1,000 milliseconds.

[00:08:31] Actually, since I know error is there, I can just do null to be even more clear. There's no error that we're passing along.
>> Kyle: I'll leave that code up for just a second so you guys can copy it down.
>> Kyle: What we're doing here is asynchronously reading in the file.

[00:09:00] When we get the contents back, which could take a couple milliseconds for it to do. When we get the contents back, we then check to make sure that it was successful. If it was not successful, we immediately error out. But if it was successful, we simulate going and doing some other asynchronous thing.

[00:09:15] In this case, we use a timer to simulate something like a database call. So now, when we switch back to our command line. I'll clear my command line. When we run our code with an error file in it, we immediately get an error back. But when I run our code with a real file that is going to succeed, it waits one second before it prints the output to us.

[00:09:44]
>> Kyle: Did everybody get to that point?
>> Kyle: Take a moment and take, what's that?
>> Speaker 4: [INAUDIBLE]
>> Kyle: Yeah. Take a moment and take stock of where we've come so far. You already are writing, I mean, I know it's silly, stupid, Hello World stuff. But you're already writing non-trivial Node logic.

[00:10:05] You're handling asynchronicity. You're handling error conditions. You're handling printing out a help. You're handling parsing your command lines. You're doing real tasks that you can use in your real-world, every day coding to write real Node programs. And we haven't even scratched the surface of the stuff that we're gonna get to after launch.