Notes of Maks Nemisj

Experiments with JavaScript

Error handling in isomorphic node.js application

When preparing application for deploying to production env I want to ensure that everything is properly logged, especially things which are unexpected in code. That’s why, I think, proper error handling is crucial to any application.

This is a short article on how to do an error handling when going live in isomporhic web app using React.js, Express.js and Fluxible. Probably after seeing the word Fluxible you might think – “I don’t use Fluxible, it is irrelevant to me”. Still hold on, since most of the points can be applied to any isomporhic SPA’s based on Express and React.

Step 1: Rendering

First step is to prevent initial rendering from breaking. It is the place in a code where method render() is called on react-dom and renderToStaticMarkup() on the react-dom/server.

An example code for browser:

import ReactDOM from 'react-dom';
try {
  ReactDOM.render();
} catch (e) {
  console.error(e);
}

and one for the server:

import ReactServer from 'react-dom/server';
try {
    ReactServer.renderToStaticMarkup();
} catch (e) {
  console.error(e);
}

In case you use promises in your code base, there is no need to put catch statements around methods. Instead use catch() function. Code below will clarify it:

import ReactServer from 'react-dom/server';

  (...some code before)
  .then(() => {
    ReactServer.renderToStaticMarkup();
  })
  .catch(() => {
    console.error(e);
  });

Step 2: Express

After rendering of react is fixed, there are other things, which might go wrong on the server. For example, things might break before the rendering. If you use Express.js you can catch them using special middleware: http://expressjs.com/en/guide/error-handling.html

This middleware should be placed after all the other middlewares:

import express from 'express';
const server = express();
server.use((req, res) => {
  // some rendering code
});

server.use((req, res) => {
  // some other handler
});

//// error middleware is HERE:
//

server.use((err, req, res, next) => {
  console.error(err);
});

//
////

As you can see, this middleware expects to receive 4 arguments, the first one is err object and all others are as in normal middleware.

Step 3: Global error handler

Besides the specific error handlers, there are also two global places for intercepting errors which can be used:

Step 3.1: Node.js

Node.js has a global error hook to catch the errors inside the asynchronous flows. Such errors might occur, when you’re getting back from I/O inside a callback and the code is not within try {} catch() {}. Example:

import superagent from 'superagent';

export default () => {
  return new Promise((resolve, reject) => {
    superagent
      .get(url)
      .end((err, result) => {
        // at this place if error occurs, global hook can help
        return resolve(result);
      });
  });
}

To setup global error hook use uncaughtException event:

process.on('uncaughtException', (err) => {
  console.log(err);
});

A lot of people advice against using this hook, but I propose to do it if you do some different logging, than console.error. At least, you could catch error using your logger, and then terminate a process:

process.on('uncaughtException', (err) => {
  logger.error(err);
  process.exit(1);
});

If you use Promises in your code base, or maybe some of the dependencies might use them, there is another event available: unhandledRejection. This one will catch promises which have no .catch() method in them. Example:

  (some code)
  .then(() => {
    // at this place if error occurs, unhandledRejection might help
  });

Here is the hook to use:

process.on('unhandledRejection', (reason, p) => {
  console.error(reason);
});

Small note to those who use source-map-support npm package. In order to make uncaughtException to work you have to disable it inside the module configuration:

require("source-map-support").install({
  handleUncaughtExceptions: false
});

Step 3.2: Browser

When code is running inside the browser, there is another way to catch unhandled errors. Such errors might occur not only inside fetching of the data, but for example inside browser events, like mouse clicks, key presses, scrolls. To setup error handler use window.onerror

window.onerror = function onError(message, fileName, col, line, err) {
 console.error(err || new Error(message));
});

Be careful with the non production build of React. It appears that React intercept unhandled messages with the ReactErrorUtils and will give you Script error instead of meaningful error. WHen you will build the react for production then all will be fine.

Step 4: Fluxible

Fluxible has its own way of handling errors. Whenever you use executeAction, it will be caught by the Fluxible itself. Which means it wont’ appear in all of the above places. In case you want to get the error and do something with it, use componentActionErrorHandler when constructing Fluxible instance:

new Fluxible({
  componentActionErrorHandler(context, fluxibleError) {
    // fluxibleError has err inside which is Native one 
    console.error(fluxibleError.err);
  }
})

Step 4.1: Services

It’s not a separate hook, but a friendly reminder. Do something with your errors inside the services, when fetching data. I have noticed that it is one of the points where people forget to do the error handling.

Step 5

Whenever you use some framework or library, don’t hesitate to look into the documentation. Maybe they have their own way of handling errors. Please, do not leave your luggage errors unattended.

, , , , , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.