Failed to Execute 'json' on 'response': Body Stream Already Read

It is not hard to see that some people are struggling to handle errors, and some are fifty-fifty totally missing it. Handling errors properly ways non but reducing the evolution fourth dimension by finding bugs and errors easily only also developing a robust codebase for large-scale applications.

In item, Node.js developers sometimes notice themselves working with not-then-clean lawmaking while handling various kinds of errors, incorrectly applying the aforementioned logic everywhere to deal with them. They just proceed request themselves "Is Node.js bad at handling errors?" or If non, how to handle them?" My respond to them is "No, Node.js is nifty at all. That depends on u.s.a. developers."

Here is one of my favorite solutions for that.

Types of Errors in Node.js

First of all, information technology is necessary to take a articulate understanding of errors in Node.js. In full general, Node.js errors are divided into two distinct categories: operational errors and developer errors.

  • Operational errors represent runtime problems whose results are expected and should be dealt with in a proper way. Operational errors don't hateful the awarding itself has bugs, but developers demand to handle them thoughtfully. Examples of operational errors include "out of memory," "an invalid input for an API endpoint," and then on.
  • Programmer errors represent unexpected bugs in poorly written code. They mean the code itself has some issues to solve and was coded wrong. A skillful example is to try to read a belongings of "undefined." To fix the effect, the code has to be changed. That is a bug a developer made, not an operational error.

With that in mind, you should have no trouble distinguishing between these two categories of errors: Operational errors are a natural part of an awarding, and developer errors are bugs caused past developers. A logical question that follows is: "Why is information technology useful to divide them into two categories and deal with them?"

Without a clear understanding of errors, you might feel like restarting an awarding whenever an error occurs. Does information technology make sense to restart an application due to "File Not Found" errors when thousands of users are enjoying the awarding? Absolutely not.

But what about programmer errors? Does it brand sense to keep an awarding running when an unknown problems appears that could result in an unexpected snowball event in the application? Again, definitely non!

It'south Fourth dimension to Handle Errors Properly

Assuming you accept some experience with async JavaScript and Node.js, you might have experienced drawbacks when using callbacks for dealing with errors. They force you to check errors all the style down to nested ones, causing notorious "callback hell" issues that make information technology difficult to follow the code flow.

Using promises or async/await is a good replacement for callbacks. The typical code flow of async/wait looks similar the following:

          const doAsyncJobs = async () => {  try {    const result1 = expect job1();    const result2 = await job2(result1);    const result3 = wait job3(result2);    return await job4(result3);  } grab (mistake) {    panel.mistake(error);  } finally {    await anywayDoThisJob();  } }                  

Using Node.js congenital-in Error object is a good practice because information technology includes intuitive and articulate information about errors similar StackTrace, which near developers depend on to proceed runway of the root of an fault. And additional meaningful properties like HTTP status lawmaking and a description past extending the Fault class will make it more than informative.

          class BaseError extends Mistake {  public readonly name: string;  public readonly httpCode: HttpStatusCode;  public readonly isOperational: boolean;    constructor(name: string, httpCode: HttpStatusCode, clarification: string, isOperational: boolean) {    super(description);    Object.setPrototypeOf(this, new.target.prototype);      this.name = name;    this.httpCode = httpCode;    this.isOperational = isOperational;      Error.captureStackTrace(this);  } }  //free to extend the BaseError class APIError extends BaseError {  constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, clarification = 'internal server error') {    super(name, httpCode, isOperational, clarification);  } }                  

I but implemented some HTTP status codes for the sake of simplicity, just you are free to add together more afterwards.

          export enum HttpStatusCode {  OK = 200,  BAD_REQUEST = 400,  NOT_FOUND = 404,  INTERNAL_SERVER = 500, }                  

There is no need to extend BaseError or APIError, but it is okay to extend it for mutual errors according to your needs and personal preferences.

          grade HTTP400Error extends BaseError {  constructor(description = 'bad request') {    super('NOT FOUND', HttpStatusCode.BAD_REQUEST, true, description);  } }                  

So how do you use information technology? Just throw this in:

          ... const user = await User.getUserById(ane); if (user === null)  throw new APIError(    'Not FOUND',    HttpStatusCode.NOT_FOUND,    true,    'detailed explanation'  );                  

Centralized Node.js Error-handling

Now, nosotros are set to build the main component of our Node.js error-handling arrangement: the centralized fault-handling component.

Information technology is usually a good thought to build a centralized mistake-treatment component in gild to avert possible lawmaking duplications when treatment errors. The error-handling component is in charge of making the caught errors understandable by, for example, sending notifications to system admins (if necessary), transferring events to a monitoring service like Sentry.io, and logging them.

Here is a basic workflow for dealing with errors:

Error handling in Node.js: basic workflow

In some parts of the lawmaking, errors are defenseless to transfer to an error-treatment middleware.

          ... try {  userService.addNewUser(req.body).then((newUser: User) => {    res.condition(200).json(newUser);  }).catch((error: Error) => {    next(error)  }); } catch (error) {  next(mistake); } ...                  

The error-handling middleware is a proficient identify to distinguish between error types and ship them to the centralized error-handling component. Knowing the basics near handling errors in Express.js middleware would certainly assistance.

          app.utilise(async (err: Mistake, req: Request, res: Response, adjacent: NextFunction) => {  if (!errorHandler.isTrustedError(err)) {    next(err);  }  await errorHandler.handleError(err); });                  

By now, one can imagine what the centralized component should look like because we have already used some of its functions. Bear in mind that it is totally up to y'all how to implement information technology, but it might look like the following:

          form ErrorHandler {  public async handleError(err: Error): Promise<void> {    await logger.error(      'Mistake bulletin from the centralized fault-treatment component',      err,    );    await sendMailToAdminIfCritical();    look sendEventsToSentry();  }    public isTrustedError(error: Error) {    if (error instanceof BaseError) {      return error.isOperational;    }    render simulated;  } } export const errorHandler = new ErrorHandler();                  

Sometimes, the output of the default "panel.log" makes it difficult to keep track of errors. Rather, it could be much better to print errors in a formatted manner so that developers tin can apace understand the bug and brand sure they are fixed.

Overall, this will salvage developers fourth dimension making information technology piece of cake to keep rails of errors and handle them past increasing their visibility. It is a expert decision to apply a customizable logger like winston or morgan.

Here is a customized winston logger:

          const customLevels = {  levels: {    trace: five,    debug: 4,    info: 3,    warn: two,    error: 1,    fatal: 0,  },  colors: {    trace: 'white',    debug: 'green',    info: 'green',    warn: 'yellowish',    error: 'cherry-red',    fatal: 'red',  }, };   const formatter = winston.format.combine(  winston.format.colorize(),  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),  winston.format.splat(),  winston.format.printf((info) => {    const { timestamp, level, message, ...meta } = info;      render `${timestamp} [${level}]: ${message} ${      Object.keys(meta).length ? JSON.stringify(meta, zilch, 2) : ''    }`;  }), );   form Logger {  private logger: winston.Logger;    constructor() {    const prodTransport = new winston.transports.File({      filename: 'logs/error.log',      level: 'error',    });    const transport = new winston.transports.Console({      format: formatter,    });    this.logger = winston.createLogger({      level: isDevEnvironment() ? 'trace' : 'error',      levels: customLevels.levels,      transports: [isDevEnvironment() ? ship : prodTransport],    });    winston.addColors(customLevels.colors);  }    trace(msg: any, meta?: any) {    this.logger.log('trace', msg, meta);  }    debug(msg: any, meta?: any) {    this.logger.debug(msg, meta);  }    info(msg: any, meta?: any) {    this.logger.info(msg, meta);  }    warn(msg: any, meta?: any) {    this.logger.warn(msg, meta);  }    error(msg: whatsoever, meta?: whatsoever) {    this.logger.error(msg, meta);  }    fatal(msg: any, meta?: any) {    this.logger.log('fatal', msg, meta);  } }   consign const logger = new Logger();                  

What it basically provides is logging at multiple dissimilar levels in a formatted way, with articulate colors, and logging into unlike output media co-ordinate to the runtime environs. The practiced thing with this is y'all can watch and query logs past using winston's born APIs. Furthermore, you tin apply a log analysis tool to analyze the formatted log files to become more useful information about the application. It's awesome, isn't information technology?

Up to this point, nosotros mostly discussed dealing with operational errors. How almost programmer errors? The best way to deal with these errors is to crash immediately and restart gracefully with an automatic restarter like PM2—the reason being that developer errors are unexpected, every bit they are actual bugs that might cause the application to end up in a wrong state and behave in an unexpected way.

          procedure.on('uncaughtException', (fault: Error) => {  errorHandler.handleError(error);  if (!errorHandler.isTrustedError(error)) {    process.get out(i);  } });                  

Last but not least, I am going to mention dealing with unhandled promise rejections and exceptions.

You might find yourself spending a lot of time dealing with promises when working on Node.js/Express applications. It is not hard to see alarm messages about unhandled promise rejections when you forget to handle rejections.

The alert letters don't practise much except logging, simply it is a good practice to utilise a decent fallback and subscribe to process.on('unhandledRejection', callback).

The typical error-handling menstruum might wait like the following:

          // somewhere in the code ... User.getUserById(i).and so((firstUser) => {   if (firstUser.isSleeping === false) throw new Error('He is non sleeping!'); }); ...   // become the unhandled rejection and throw it to another fallback handler we already have. procedure.on('unhandledRejection', (reason: Mistake, promise: Promise<whatever>) => {  throw reason; });   process.on('uncaughtException', (mistake: Error) => {  errorHandler.handleError(mistake);  if (!errorHandler.isTrustedError(mistake)) {    procedure.leave(1);  } });                  

Wrapping Up

When all is said and done, you should realize that mistake-handling is non an optional actress but rather an essential role of an application, both in the evolution stage and in product.

The strategy of treatment errors in a single component in Node.js will ensure developers save valuable time and write clean and maintainable code by avoiding code duplication and missing fault context.

I hope you enjoyed reading this article and establish the discussed error-handling workflow and implementation helpful for building a robust codebase in Node.js.


Further Reading on the Toptal Engineering Blog:

  • Using Express.js Routes for Hope-based Error Handling

bertlesresuresse.blogspot.com

Source: https://www.toptal.com/nodejs/node-js-error-handling

0 Response to "Failed to Execute 'json' on 'response': Body Stream Already Read"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel