
Introduction to Node.js, v2 Error Handling
Transcript from the "Error Handling" Lesson
[00:00:00]
>> So, I have just been writing node.js code JavaScript code pretty irresponsibly. Just now if we just look at the code that I wrote, at any point, this code could throw an error. Like for instance, if I decided to not put HTML and we tried to run this, as you can see that I get an error.
[00:00:21] Maybe that is exactly what I want. In this case, I do want it to error out if I didn't provide proper arguments to the methods and stuff. But in some cases, you probably don't want your whole app to crash and burn, because there wasn't a known error that you probably anticipated and it doesn't affect things.
[00:00:39] Something like, imagine if you had an API that needs to be. If you wanted to make a request to the API, you had to authenticate your request. And you had to pass like a token or some type of, API key or something like that. Now, imagine your database throwing an error, if that was an invalid token, or if that user wasn't found.
[00:01:01] If that ever did what this just did as an app crashed my entire app. That means if someone doesn't provide the correct credentials to your server, all your users will be punished your whole app crash simply because someone just fat fingered API token or they're trying to hack you or whatever.
[00:01:16] So, you don't wanna give them that power. So, sometimes you don't want the whole app to crash, because there was an error. So, you need some type of way of listening for errors, catching errors, handling errors, and node.js has some pretty good ways to do that. So, I wanna walk through how you can do that with node.js because I think it is very important.
[00:01:36] To understand that, so that's what we'll do. So, the first thing that I wanted to talk about when it comes to errors is really just understanding the process of node.js. What that is and how that runs. So, when an exception is thrown in node.js or an error, the current process will exit with a code of one fact we might see that here.
[00:01:59] Let's see the say, exit one, somewhere. No it doesn't. But this actually errored out an exception was thrown in a process exited literally with one. So, let's go into node I'm gonna type in process.exit and I'm gonna say 1. And what this really does is when you exit with the number of 1 it's telling node that it exited because something bad happened and broke.
[00:02:27] If I exit with like a 0 or non 1. That means like, yeah, it was successful. Nothing happened, we're just done. And that's very useful for when you're like looking at logs. You're trying to debug something to see why your code stop running. What caused it, wasn't an error or that it just completed a job and we just wrote back code like what was it and that's what useful for.
[00:02:51] It's also useful for like piping that information into another process or a log file or something like that. So, that's how the process is gonna to work. And that's gonna to happen automatically. If you don't handle your errors as you saw not through the error, it immediately crashed.
[00:03:06] Nothing else was executed, it broke. Kinda similar to what you would get in the browser. I would imagine if you threw an error here. It would also, break, but it will continue to move forward depending on how you wrote your code, how you code it, different things like that.
[00:03:23] So we kinda wanna do the same thing here. Whereas except on the browser, if a JavaScript error happened and it didn't catch, your whole screen is not gonna go blank. I guess the less that's what it was trying to do in the first place. But chances are you'll just have these errors in your console.
[00:03:38] And at least people can still see your website or they can, still scroll through or maybe, depending on what the interaction is. But in node.js, if your whole app crashes, it crashes there's there's nothing left. There's no interaction with it. There's nothing other than the fact that it should be on.
[00:03:52] There's no UI, so, you don't get the benefit of like, well, I guess people could still at least use it until we fix it. Nope, it's pretty much done. So, you want to get around that, let's look at these docks here. Here we go. So, that's how the process exiting works.
[00:04:08] Now I showed you that not because you should be using it, you should almost never be using process.exit ever. And I'm gonna say the biggest reason why you would not want to do that is because you just don't get the chance to catch the error. If you're literally exiting every single time, then you're not giving yourself the chance to, well, what type of error is it?
[00:04:29] Well, I only wanna exit if it's this or only wanna do that. You don't have to do it. Node.js is gonna exit for you. You just throw an exception. That's all you really have to do. You need to be responsible for what exceptions you wanna throw and what exceptions you don't want to throw.
[00:04:44] So, that's the only responsibility that you need to have when it comes to errors in node.js. So, let's talk about how we used to handle errors in node.js maybe now we probably still do depending on your code, and then we'll kind of walk through like the different ways to do it with different scenarios.
[00:04:59] So the first one is async errors. So let's go back to our file here, I'm gonna stay in here or actually we can do file will call this like errors.mjs and I'm gonna import. Let's import read file again, but I'm not gonna be the promised one the read file from fs not from promise, I'm gonna say read file.
[00:05:22] And I'm going to do the same thing I have before so in the URL, in this case, I will just get a new file. This time I'm gonna read the app.mjs. Just a really toss it up and imports.meta.url. I'm gonna do that and then I'm going to do the UTF for the encoding to give us a string, and then I'm gonna pass a callback and here's the error handling thing.
[00:05:52] So, as you can see here, as I hover over this callback, you can see that it's saying here are the two arguments that you might possibly get. You could possibly get an error, as the first argument and then you could get the result the data as a second argument.
[00:06:05] And this is how we handled errors in the early days of node. And like I said, we still do cuz it's still very popular. If you're not using promises or async await. You would use callbacks. If you are using callbacks for asynchronous code. Typically, the pattern is this, you have an error that is either going to be there or it's going to be no.
[00:06:23] And then you have the result or the data which will be there if there is no error or if there is data. And that's how you would handle it. So then in this callback, you would say something like, well, if error, I want to do something, right, so this is where you handle the error.
[00:06:38] And what I mean by handle, I mean, you decide on what you want to do there like is this read file method so crucial to your app that if it doesn't work, you need to stop. Or is there some type of fallback that you can do or is this not that important?
[00:06:54] Or whatever, do you need to report this error to some management app that you are paying for that like logs, your errors that you can go tap into GitHub later look at them something like century or something like that. Then this is where you would do that. Otherwise I don't have an error, so, I got my data.
[00:07:09] So, I'm gonna do whatever I want with my data here, right? So let's try to trigger this to see what happens. So I'm gonna say error. We're gonna console.log the error here. Or actually I'm gonna say console.error the error. Which doesn't really do anything different visually but there's gonna be semantics here.
[00:07:27] So, now I'm going to actually make this error outs by changing the file name. And then we're going to run node against errors.mjs. And as you can see, we do get an error, no such file name or directory, because that's not a real thing and I'm logging it because that's literally what's happening.
[00:07:45] We have an error. But if I didn't want this to crash, what I could do. Hold on. There we go. Yeah, if I didn't want this to crash, I would. First of all, it's not crashing right now it's just logging it. You can see that it's not actually crashing.
[00:08:00] It's just logging the error but looks like it's crashing. So, if I wanted to do something with this, like let's say maybe I did want this to crash because this is a crucial piece of code, then I would just re-throw the error. So like, okay, cool, actually, yeah, this is the code.
[00:08:14] This is the error that if it happens, I want it to crash, cool. Yeah, throw it and now you can see that the error was thrown. The code won't go on, nothing's gonna be completed we are good to go. So this is why it's really important to be able to use the things is built into nodes to handle the errors versus just sticking with a process dot exit on everything.
[00:08:31] Which is what I've seen before, which is just don't get the opportunity to do anything. And then obviously, there was no error. We will do some stuff out here. So, this is how you would handle errors ,async errors that use callbacks. Now, if we wanted to handle an error that was, promised based, it's pretty similar to be honest.
[00:08:50] It depends if you wanna use promises ,or async await, but it looks something like this. So, if I'm gonna change this to fs promises. And we don't use a call back anymore because we got a promise. So we'll do that and now we can get the results. And we can await this like this.
[00:09:13] So now if we have this and we run it it's gonna air out and it's gonna break. And you can see that we do get this error here. But if we want it to catch this error, cuz we don't know what type of error it's going to be.
[00:09:25] Maybe we wanna report it. We would have to wrap this in a try catch, if we're gonna do await. So we'd say try, and then catch, okay. Just like you will don't synchronous code because await is supposed to feel synchronous. So we treat it like synchronous code and this is how you would catch an error for something synchronous.
[00:09:43] You'd wrap it in a truck catch. And now the error is gonna bubble up to this catch right here without breaking the code. So, you just to show you I'll say console.log error (e) and then you can see it will still move on to this next line, which means it did break.
[00:10:02] So you can see we did catch the error, we logged it and then we moved on to the next line because the code continued to move on because we caught the error. We just happened to log it. And then just like before, it was like no, actually this error is important.
[00:10:14] We do wanna throw this one we can throw it, it won't move on to the next line. And the whole app actually crashed. So you get that choice, you get that power of determining on how you want to do it. With promises, it's pretty much the same thing, you just don't use await.
[00:10:28] I mean, it's pretty much the same thing as a callbacks, not the try catch. You don't use the await. And you just use the native like dot catch inside of a promise, which gives you an error like this. And then now you can put your doing the exact same thing you did with the callbacks, if it's this type of error throw and if it's not, don't do anything.
[00:10:49] It's already handled for you. So, that's how you you would do that in a promise based workflow with error handling. So, that is how you would handle the errors that you had control over as if you were writing the code. And you knew where errors were going to potentially happen.
[00:11:07] But let's say you did not have control of the code as if you were using some type of library. You download it, that's internal somewhere and you just don't have the ability to catch it somehow. Or maybe you don't want to, I don't know, maybe there's just an arrow.
[00:11:21] You just don't know what's going on, you can't really get this error. Then you do have this catch all implementation, which is gonna be called process.on uncaught exceptions. So let's talk about that. So, if you have an async error like this one, so I'm gonna wait this. I'm not going to try catch it.
[00:11:40] But what I am gonna do is before I actually call this function, which will cause the error, I'm gonna register a process.at, that process. Okay, I don't know why it's doing that, don't autocomplete, thank you. Sometimes, autocomplete is like, way too aggressively helpful. I don't even know what that wasn't even heard of that variable that it was kind of autocomplete.
[00:12:02] So, process.on uncaught exception that was called, let me see, yeah on caught exception. There we go. I don't know why I couldn't spell there. Okay, processor on uncaught exception and then we get a callback here. And you can see that we'll have some type of error. Let's log that.
[00:12:24] And we'll run the code. And you can see that and just to show you that we'll continue moving on. Let's see what happens. Let's see what happens if we move on here. So, what's happening here is that the error was thrown, and there was nothing we can do about it.
[00:12:41] But, it did not actually get caught cuz we didn't see line nine move forward. Actually, we were able to see what error was thrown but it's too late arrows already thrown. We can't catch it. And this is why I said you don't wanna use the process.exit and stuff because you don't get the chance to catch the error.
[00:13:00] If this was an optional error that a user did or something, there's nothing I can do about it. I'm just registering for uncaught exception or process exit. You don't wanna do that. So you do wanna do the try catch, you do wanna do the dot catch, you do wanna do callback error data pattern.
[00:13:16] And only save this for like, okay, someone didn't catch this error. What is going on? Maybe we need to log this somewhere, but by the time you get here, you can't catch it. It's already been thrown. That's why it's called uncaught exception. No one caught it. So, this is just for helping you figure it out what happened, why your thing crashed and maybe do some cleanup logic some things like, it's about the crash dude do this right quick.
[00:13:44] You're able to do that. And I'm pretty sure you can't do async stuff in here. So, if you wanna like, I need to clean the database before the server starts down because error was thrown, you can't do it. You can only really do synchronous things. Because the way the event loop works, you're pretty much on the last tick of the event loop before this application bottoms out.
[00:14:03] They're not gonna it's not going to restart not gonna queue up any more tasks for you because it's scheduled to be shut down. So, you can't do anything that requires anything to be queued up so anything asynchronous can't happen here. So you got to keep it moving. So yeah, this is just for like, if you're just lost and you can't figure out why something is happening, you would do this at the top of your app somewhere.