Trying to Read a Large File and Got a Fatal Error Node.js
Node.js error handling isn't a walk in the park. When deploying applications into product, we desire to know that all code has been tested for all possible complex scenarios.
We can, however, avoid issues by preparing our lawmaking to properly handle errors. To do and so, nosotros need to empathise:
- Fault object
- Endeavor…catch
- Throw
- Telephone call stack
- Effective function naming
- Asynchronous paradigms like promise
Today we're going to have you through the above items and give you a tour of JavaScript'southward chief quirks for better insight and agreement. Then, we'll hash out best practices and common stumbling blocks that developers often struggle with when dealing with Node.js error handling.
Then let's go to it.
Node.JS Error Handling: Why Bother?
Commencement allow's accost the big question: why bother handling errors at all?
Imagine you're using Node.js to build a RESTful web API. You have users who are sending requests for data from your server. All practiced so far, right?
Let'due south look at an example. Imagine you're using Node.js to build a RESTful web API. You lot take users who are sending requests for data from your server. All good so far, correct?
With anyone in the world throwing requests at our program, it's only a thing of time before values go into our program that nosotros didn't wait. And when we get these values, effectively responding to our users ways being as descriptive equally possible nigh the error. If nosotros don't accept proper Node.js error handling, it'due south probable we'll throw a generic error message. So the user volition run into the fault message "Unable to complete request."
This is not very useful.
Furthermore, it's not just the user experience that dictates why we should exist savvy with our Node.js error treatment. If we want our programme to exist secure, resilient, high-performing and problems-costless, Node.js error handling is a must.
Fault Object
The first matter to know most Node.js error handling is the mistake object.
You might accept seen some code that looks like this:
throw new Error('database failed to connect'); That may sound pretty overwhelming.
For simplicity, permit's interruption it down. 2 distinct things are happening hither: the error object is existence created and is being thrown. Permit's showtime by looking at the error object and how it works and get back to the throw keyword a little subsequently.
Despite their scary exterior, fault objects are pretty straightforward. The mistake object is an implementation of a constructor function that uses a set of instructions (the arguments and constructor body itself) to create an object. That's it. The born error constructor is simply a unified way of creating an error object.
What are error objects, anyhow? Why do they need to exist uniform?
These are important questions, then let's get to them.
Beefcake of an error object
The first argument for a native error object is its description. The description is the homo-readable string of your mistake object. It's what pops up in your console when something goes awry.
2d, error objects also have a name property, which is the computer-readable part of the object. When you apply the native error object, the proper name property defaults to the generic "Error." However, you can also create your own. The best way to do this is by extending the native error object like then:
class FancyError extends Error { constructor(args){ super(args); this.name = "FancyError" } } console.log(new Error('A standard error')) // { [Fault: A standard mistake] } console.log(new FancyError('An augmented mistake')) // { [Your fancy mistake: An augmented error] proper name: 'FancyError' } Nosotros mentioned before that nosotros want our error objects to be uniform. That's considering when we throw an error, it helps to have consistency in the thrown object. If we have consistency in our objects, error handling is easier (more on this later on).
At present let's discuss throw, our next piece in the puzzle.
Uh-oh, an error – throw it!
Creating an error object is not the end of the story and simply metaphorically primes our error for sending. How we hit send on our error is by throwing. But what does it mean to throw? And what does that mean for our plan?
Throw actually does two things: it stops the plan, and it finds a grab to execute. Let's examine these ideas one at a time.
When JavaScript finds a throw keyword, the get-go thing information technology does is end dead in its tracks. This event prevents whatever more functions from running. Past stopping like this, information technology mitigates the chance of any farther errors occurring and helps debug programs easily.
With the program halted, JavaScript volition begin to trace the daisy chain of functions that were called in order to accomplish a take hold of statement. This chain is chosen the call stack (don't worry – we'll get to the call stack soon). The nearest catch that JavaScript finds is where the thrown exception emerges. If no endeavour/catch is plant, the exception throws, and the Node.js process exits, causing the server to restart.
Throwing by instance
So far, we've been quite theoretical. Allow's accept a expect at an example:
part doAthing() { byDoingSomethingElse(); } function byDoingSomethingElse() { throw new Mistake('Uh oh!'); } part init() { try { doAthing(); } catch(e) { panel.log(due east); // [Fault: Uh oh!] } } init(); Here we can encounter that the init function has try/catch error handling in place. It calls a function, which calls another part, which in plow throws an error. Information technology is at the betoken of mistake when the program halts and begins tracing the role that threw the mistake. Somewhen information technology gets back to the init function and executes the catch statement. In the catch statement, we tin can decide to take an activity, suppress the fault, or even throw another error (to propagate upward).
Call stack
What we're seeing in the above example is a worked example of the telephone call stack. In order to function (like most languages), JavaScript utilizes a concept known every bit the call stack. But how does a call stack work?
Whenever a part is called, it's put onto the stack; when it's returned, it's removed from the stack. Information technology is from this stack that nosotros derive the proper name "stack traces," which you lot might have heard of. These are the large, scary-looking letters that nosotros sometimes see when something is buggy in our program.
They often look like this:
Error: Uh oh! at byDoingSomethingElse (/filesystem/aProgram.js:7:11) at doAthing (/filesystem/aProgram.js:3:5) at init (/filesystem/aProgram.js:12:9) at Object.<anonymous> (/filesystem/aProgram.js:19:i) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
At this signal, y'all might be wondering how a call stack helps u.s. with Node.js error handling. Allow's talk about the importance of telephone call stacks.
Your call stack provides your breadcrumbs, helping yous trace back the style in which you lot came. The call stack also enables you to trace the origin of a failure and so yous can easily see which functions were called. In improver, it helps trace when the run-up to an error was created.
Only wait, there'due south some nuance to all this call stack stuff.
Where this gets hairy is the topic of anonymous functions, or functions that don't have names. Why, yous ask, would we have functions without names? Sometimes in our programs, we want to define small, throw-away functions that practice a small-scale matter. We don't want to labor ourselves with the job of naming them, simply information technology's these anonymous functions that can cause us all kinds of headaches. An anonymous office removes the part name from our telephone call stack, which makes our telephone call stack significantly harder to use.
Note that naming functions in JavaScript is not that straightforward. So, let's take a quick look at the unlike ways that you lot can ascertain functions and address some of the pitfalls in office naming.
How to name functions
To understand function naming, let's look at some examples:
// Anonymous function const one = () => {}; // Anonymous functions const two = function () {}; // Explicitly named function const 3 = function explicitFunction() {}; Here we've got three example functions.
The first is a lambda (sometimes called a fatty arrow part). Lambda functions are anonymous past virtue. Don't go confused. The variable name "1" is non the function proper noun. The function name is optionally passed after the keyword "part." Merely in this example, we're not passing anything at all, so our function is anonymous.
Note: It doesn't help that some JavaScript runtimes, like V8, can sometimes guess the proper name of your function. This happens even if you don't requite it one. But without digressing too much, you don't desire to rely on this functionality considering it's not consistent.
Second, we've got a function expression. This is very similar to the commencement. It's an anonymous function, but simply defined with the office keyword rather than the fat arrow syntax.
Finally, we take a variable announcement with an apt name: explicitFunction. Information technology shows that the only role that is accordingly named is when you employ the function keyword and pass the (optional) name. Information technology's generally a best practice to provide this proper noun wherever you lot can to have more readable stack traces.
Treatment async (callback) errors
At this point, you're an aficionado of the error object, throw keyword, telephone call stacks and office naming. And so, let's turn our attention to the curious case of treatment asynchronous errors. Why? Because they don't carry every bit you lot'd expect, and asynchronous programming is essential to every Node.js programmer.
Before we become as well deep, let's evaluate how JavaScript handles async tasks, and why we even need to practice this.
JavaScript is a single-threaded programming language which in evidently English, ways that JavaScript runs using a single processor. By virtue of having a single processor, we get blocking, or conversely, non-blocking code. Blocking code refers to whether your plan volition wait for an async task to be consummate earlier doing anything else. Whereas non-blocking code refers to where y'all register a callback to perform when the task completes.
Essentially, information technology'due south worth mentioning that in that location are two main ways you tin can handle async in JavaScript: promises or callbacks. We're deliberately ignoring async/await here to avert confusion because information technology's simply sugar on top of promises.
For the purpose of this article, we'll focus on promises. There is considerable industry consensus that for application coding, promises beats callbacks in terms of programming style and effectiveness. So for this article, we'll ignore the callback pattern and assume that you're going to opt for promises instead. A fine choice, we might add.
Note: Luckily for you, in that location are many means to convert your callback-based code into promises. For case, you can use a utility similar the built-in promisify or wrap your callbacks in promises like and so:
var request = crave('request'); //http wrapped module function requestWrapper(url, callback) { request.go(url, function (err, response) { if (err) { callback(err); } else { callback(cipher, response); } }) } Nosotros'll handle this fault, I hope!
Okay, so nosotros've doubled down on promises. Only what does this mean for our Node.js mistake treatment?
Allow's take a look at the anatomy of a promise.
A promise in JavaScript is an object that represents a future value. Promises allow us to model asynchronous lawmaking like synchronous lawmaking by using the Promise API (which we'll run across later). Information technology's also worth noting that a hope usually comes in a chain, where one action executes, then another and so on.
Just what does this all mean for Node.js error handling?
Promises handle errors quite elegantly and volition catch whatever errors that preceded it in the chain. This is great for Node.js programmers because we can handle many errors in many functions within a single handler. Let's take a await a the code beneath:
function getData() { render Promise.resolve('Exercise some stuff'); } office changeDataFormat(){ // ... } function storeData(){ // ... } getData() .then(changeDataFormat) .then(storeData) .catch((due east) => { // Handle the fault! }) Hither we run across how you tin can roll upwardly your fault handling for all 3 unlike functions into 1. Essentially, this behaves every bit if it's wrapping all of your functions in a synchronous try/catch.
To take hold of or not to catch promises?
At this point, yous might be wondering whether adding a catch to your promises is optional. Yep it'southward optional, but you lot should always provide a catch handler.
Why? Because at that place are many ways your asynchronous calls tin can neglect. Our code might timeout, it could have network issues or there might exist a hardware failure. For all of these reasons, yous should e'er instruct your program what to do in the case of a promise failure.
Remember the golden rule: always handle promise rejections.
The perils of async try/take hold of
We're nearing the stop at present on our journey through Node.js error treatment. But information technology's the right time to identify a pretty large pitfall of async code and the try/take hold of statement.
You might take been wondering why the hope exposes a catch method and why we can't just wrap our promise implementation in a try/grab. If you were to do this, the results would not be as you wait.
Let's take an example:
try { throw new Error(); } catch(e) { console.log(e); // [Mistake] } try { setTimeout(() => { throw new Fault(); }, 0); } catch(east) { console.log(e); // Naught, naught, nix, nada, non even a sound } Requite that a good wait. Do you come across what'south happening hither?
Try/grab is by default synchronous. That means that if an asynchronous function throws an mistake in a synchronous try/grab cake, no fault throws.
That's definitely not what nosotros want.
In summary, if yous're handling async error handling, you should really use the promises grab handler, which volition let yous to effectively handle the async errors. Only if y'all're dealing with synchronous code, the try/take hold of will do just fine.
If an exception throws, but no programmer sees it, was it fifty-fifty thrown?
— Ancient Zen Buddhist programming proverb
We've got some adept news and some bad news. If you're handling errors as all-time you can in your awarding, crawly! That's the good news. The bad news is that y'all're but halfway there.
Handling errors in your programme deals only with the things you know nigh. Every bit much as we tin write flawless code, mistakes happen. Rather than trying to prevent all failures from ever happening, nosotros should attempt to know when a failure happens. We should also get the most information nosotros tin can about the problem.
And that'due south where an application functioning monitoring tool comes into play. APMs come in all different shapes and sizes. Some clarify your log data or infrastructure; some capture and manage uncaught exceptions. When it comes to error handling, information technology makes sense to use an APM to go visibility into your awarding. There's no indicate spending all this time optimizing stack traces, function names and the like if all this rich information is thrown into some log file on a desolate server.
By utilizing an APM such as Retrace, you tin can get insights into how your plan is performing. You can as well see how your database queries execute, the load on your servers and so on. All of this can drastically help you meliorate the speed of debugging and remediation time.
Retrace collects a lot of contextual information about what is occurring when an error is thrown. This data can be very useful for fault reporting purposes. You tin get a list of your customers or users being affected past an mistake, enabling y'all to see every unique stack trace, URL or method name impacted by the error. Download your free two week trial of Retrace today.
Node.JS Error handling and confidence in shipping
And that's all we have for today. We hope that was a useful introduction to error handling in Node.js. At this point, you lot should feel more comfortable with the idea of throwing error objects and catching them in either synchronous or asynchronous code. Your applications volition be significantly more robust and ready for production.
You may also desire to endeavour Stackify's costless, existent-time code profiler, Prefix, to write better code on your workstation. Prefix works with .Internet, Java, PHP, Node.js, Cerise and Python.
Until next time, handle your errors and transport with confidence!
- About the Writer
- Latest Posts
Source: https://stackify.com/node-js-error-handling/
0 Response to "Trying to Read a Large File and Got a Fatal Error Node.js"
Postar um comentário