10 tips for coding with Node.js #3: how to know when (not) to throw
By David Mark Clements

This is part three of a ten part series. Here are the ten tips under consideration:

  1. [Develop debugging techniques][1]
  2. [How to avail and beware of the ecosystem][2]
  3. How to know when (not) to throw
  4. [Reproduce core callback signatures][3]
  5. Use streams
  6. Break out blockers
  7. Deprioritize synchronous code optimizations
  8. Use and create small single-purpose modules
  9. Prepare for scale with microservices
  10. Expect to fail, recover quickly[This is part three of a ten part series. Here are the ten tips under consideration:

  11. [Develop debugging techniques][1]
  12. [How to avail and beware of the ecosystem][2]
  13. How to know when (not) to throw
  14. [Reproduce core callback signatures][3]
  15. Use streams
  16. Break out blockers
  17. Deprioritize synchronous code optimizations
  18. Use and create small single-purpose modules
  19. Prepare for scale with microservices
  20. Expect to fail, recover quickly][4]

 

[throw][5]

In JavaScript, throwing is a destructive action. It’s the sledgehammer of error propagation.

In this post, we’ll examine scenarios where throwing is appropriate, when not to throw, alternatives to throwing, and best practices for exception handling. This article is quite detailed, you might want to grab a cup of heated liquid mixed with some kind of organic material (such as beans or leaves) and settle into a comfortable chair first.

node.js throwing </a>
</p> ## **Types of errors**

Let’s group errors into two broad categories: developer errors and operational errors. Developer errors are bugs in the software: a mistyped variable name, incorrect input, syntax errors and so forth.

Developer errors also include error states that occur from mishandling operational errors. Operational errors are generally predictable issues that occur beyond the control of the program. For instance, network errors or invalid user input.

## **Native throwing**

In many cases, the JavaScript engine will throw when it comes across a developer error. These errors should not be caught, the app needs to fail. In development this allows us to immediately weed out bugs and in production developers can be alerted whilst the process is restarted to retain maximum uptime.

## **When to throw operation errors**

Most operational errors are manageable and should be expected.

For example a dropped connection can be logged and retried, or in some cases replaced with cached content. There’s rarely grounds for throwing due to an operational error.

But there is a subclass of operational errors that could merit a throw. This class of errors is to do with environment misconfiguration.

For instance if a connection is dropping because authentication details are incorrect, we could throw to exit the process (after logging the error).

Almost all configuration happens during initialization, so if we’re throwing post-initialization it could be an over-aggressive approach to an error.

Node.js </a>
</p> ## **When to throw developer errors**

Since most programmer errors are caught and thrown by the JavaScript engine, there’s only a small set of developer errors we would ever need to throw on: incorrect usage of a public function or incorrect usage of a command line interface.

In the case of command-line apps we want immediate feedback, throwing can be a way to do that:

#!/usr/bin/env node
if (process.argv.slice(2).length < 2) {
  throw Error('Two args required');
}

Here’s an example of throwing from an exported function:

module.exports = function (secret) {
  if (!secret) {
     throw Error('..tell me your secret.');
  }
}

Even at that, we may want to question whether we’re giving a best effort. In the example, if the secret isn’t needed outside of our function we could generate one instead (and possibly warn about it, depending on the context).

Or otherwise, can we supply reduced functionality without the secret?

Generally, we only want to throw from functions that perform critical actions. If they’re non-essential then returning, emitting or calling back with an Error object is preferred.

Throwing during initialization to exit the process is appropriate, as a tenet of fail fast. This will quickly alert developers or system admins at the time they’re trying to start the process.

In short, think very carefully before throwing – is there something else we can do?

## **When not to throw**

It’s better to delegate the authority of process crashing to the top level of the application this is why we recommend not throwing inside modules unless they’re intended for use during initialization (and even then, consider allowing the application to decide anyway).

Throwing some time after initialization when the process is supposedly stable is less than ideal – particularly if the application is written as a monolith (all code running in one process, rather than separate micro-services).

If we follow the practice of only throwing when we explicitly expect to exit the process, then the amount of throws in our application should be minimal.

As a rule avoid throwing as much as possible after initialization.

In particular throwing errors that could have been handled without compromising stability, or without making the purpose of the process redundant, is a bad idea.

Never throw in a callback unless the callback is made at the top level of an application and the intent is to crash the process. Callbacks in modules should defer to the caller to decide how to handle an error.

Node.js</a>
</p> ## **Alternatives to throwing**

So what should we do for errors that shouldn’t turn into exceptions?

Well that depends on whether we’re dealing with a synchronous or asynchronous operation. Asynchronous operations can be packaged in several ways, the main ones being functions with callback parameters, event emitters and promises. We’ll talk about these as we go, but first let’s talk about throwing alternatives in synchronous functions.

### **Synchronous functions**

In many other languages, the normative approach to synchronous error handling would be to throw. This can be done in JavaScript too:

function tokenize(template) {
  if (notParseable(template)) { throw  Error('couldn\'t parse it guvna');  }
  var tokenObject = parse(template);
  return tokenObject;
}

However in Node.js this approach will either crash the process or demand that the function consumer uses try/catch. There are some cases where a throw is merited, but it should always be considered within a holistic context – do we actually want to crash the process or should we let the invoker decide? If we should let the caller decide, then they must try/catch which will affect performance and cause a process crash if the consumer forgets to try/catch. We’ll talk about try/catch more shortly, but for now let’s look at some alternatives to throwing in a synchronous context.

If we don’t throw from a synchronous function, how do we gracefully handle an issue and propagate the error to the invoking function.

Unlike the standard Node.js-style callback approach there is no agreed-upon normative contract for returning an error state.

One approach sometimes used in the wild is to return null on error. This makes for terser error checking but is both counter intuitive and bug prone.

function tokenize(template) {
  if (notParseable(template)) {  return null; }
  var tokenObject = parse(template);
  return tokenObject;
}

var tokens = tokenize('invalid }}template');
if (!tokens) {
  //handle error
} 

Returning null on error is discouraged, because both error and value states occupy the same space – so the error may accidentally be treated like a value. If the null response isn’t handled and there is an attempt to access a property on the null value, the process will throw.

This defeats the whole point and gives a less useful error.

Beyond this, if type coercion is being applied to an un-handled null return value elsewhere in the application we may get unexpected booleans, or worse a NaN.

Anyone who’s tried to track down the origin of a NaN will testify – this is bad juju.

A fairly standard approach is to return an Error object:

function tokenize(template) {
  if (notParseable(template)) {
    return Error('couldn\'t parse it guvna');
  }
  var tokenObject = parse(template);
  return tokenObject;
}

var tokens = tokenize('invalid }}template');
if (tokens instanceof Error) {
  //handle error
}

It’s ugly, but arguable no uglier than try/catch, and avoids the same disruption and issues associated with throw. However, again both the error state and value state have the same reference, which means the error object could be treated as a correct return value which will invariably create a bug or cause a throw somewhere else in the application. This sort of bug could be difficult to trace to it’s origin point, particularly given the asynchronous nature of JavaScript programs.

A second viable (and cleaner) approach would be to return an object with error and value properties:

function tokenize(template) {
  if (notParseable(template)) {
    return {
      error: Error('couldn\'t parse it guvna')
    };
  }
  var tokenObject = parse(template);
  return {value: tokenObject};
}

var tokens = tokenize('invalid }}template');
if (tokens.error) {
  //handle error
}

In this case, if we forget to handle the error, we’ll at some point try to interact with the value, and which point the app will likely throw. The problem should be easier to identify because we’ll see an object with an error property instead of an object with a value property, and then be able to trace that object to the function that returned it.

A variation of this approach would be to return an array instead of an object, where the first index of the array holds a potential error or null, and the second holds the value. This synchronous imitation of the error-first callback renders a neat compatibility between asynchronous and synchronous API’s (i.e. apply the error-value array to a callback). However the lack of semantic expression (e.g. if(!tokens[0]){} doesn’t communicate meaning well) tends to outweigh the little bit of sugar this gives with calling callbacks.

Yet another technique could be to write synchronous functions in asynchronous style using the standard error first callback contract (see the next section).

Benefits of writing all functions as asynchronous include having a well known standard approach to error propagation and zero refactoring if later down the line our function is changed to make an asynchronous call. Downsides could include developer confusion and additional boilerplate.

A final technique could be to use promises for both synchronous (check out  Promise.resolve) and asynchronous values. Use of promises will affect performance (particularly synchronous performance) and again would lead to more boilerplate.

No matter which approach we agree upon for a codebase, it needs to be consistently adopted and applied across the board, this is not a good area to mix and match ideas.

Propagating errors through callbacks

We’ll be talking about the “errback” pattern in detail in the next post, but here’s an example of propagating an error from an asynchronous function without throwing:

function asyncOp(input, cb) {
  if (!Array.isArray(input)) {
    return cb(Error('input needs to be an array'));
  }
  doAsyncThing({input: input}, cb);
}


asyncOp('oops', function (err) {
  if (err) {
    //do something
    return; //exit the function
  }
});

This way the caller of asyncOp can decide what to do with the error, the right thing may be to throw, or the right thing may be to send a 500 response to a client, or simply log it and move on – it all depends on context.

### **Emitting errors**

Event emitters are suitable for cases where we want to centralize a variety of asynchronous activity into a single object.

We’ll discuss Event Emitters in more detail in a later post, however here’s a quick example of emitting an error:

var EventEmitter = require('events').EventEmitter;

function makeNotifier(opts) {
  var ee = new EventEmitter;

  connectToPubSub(opts, function (err, pub) {
  if (err) { return ee.emit('error', err); }
    pub.on('message', function (msg) {
      ee.emit('notification', {type: 'message', data: msg});
    });

    pub.on('error', function (err) {
      ee.emit('error', err);
    });
  });

  return ee;
}

Ordinarily we would actually inherit from EventEmitter but for terseness we instantiating directly from it in the example.

Event emitters flatten structure of error handling, instead of passing errors up through several callbacks we just emit them from one object.

### **Rejecting promises**

Another asynchronous construct is Promises. We’re going to talk exclusively about EcmaScript 6 promises since these are now the standard. EcmaScript 6 Promises are available in Node.js v0.12 and io.js and can be polyfilled with the lie module in Node.js v0.10.

To propagate errors from promises, call the reject method.

function asyncOp(input) {
  return new Promise(function (resolve, reject) {
    if (!Array.isArray(input)) {
      return reject(Error('input needs to be an array'));

    }

    doAsyncThing({input: input}, function (err, val) {
      if (err) { return reject(err); }
      resolve(val);
    });

  });
}

asyncOp('oops')
  .then(function (val) {
    //yay
  })
  .catch(function (err) {
    //boo
  });

While a promise represents a future value, a deferred represents a future operation. The native Promise.defer method can be used to create a less nested function that does the same thing:

function asyncOp(input) {
  var deferred = Promise.defer();
  if (!Array.isArray(input)) {
    return deferred.reject(Error('input needs to be an array'));

  }

  doAsyncThing({input: input}, function (err, val) {
    if (err) { return deferred.reject(err); }
    deferred.resolve(val);
  });

  return deferred.promise;
}

Promises might not be for everyone, the syntax doesn’t appeal to some, and promises have a reputation for being heavy on the CPU. Nevertheless evidently they are here to stay. The potential of native promises opens doors for performance, optimizations and even at that it comes to CPU cycles for an abstraction around an asynchronous operation CPU usage is less of a concern – since the bottleneck tends to be the asynchronous operation. Later on we’ll be talking about Promises and throwing in greater detail.

## **Why to not try-catch**

JavaScript is a dynamic asynchronous language but try-catch is a synchronous construct originally designed for compiled languages.

When it comes to JavaScript, the try-catch is something of a square peg in a round hole, which can lead to developer confusion.

For instance, consider the following:

try {
  setImmediate(function () { throw Error('Oh noes!'); });
} catch (e) {
  console.log('Gotcha!', e);
}

The error won’t be caught at all. The setImmediate function is an asynchronous operation so the throw happens after the try-catch block has been executed.

Creating functions that expect the try-catch pattern can lead to poor code quality. Beyond documentation or reading source code, we have no way to know whether a function could throw an error. Typically functions that may throw can work fine most of the time, which makes it very easy omit the try-catch – until that one time when the process crashes. Or else the opposite can occur, a super defensive style of programming where we try-catch every single call (believe me, it’s not sustainable).

This aside, the requirement to try-catch at runtime demands significant hoop-jumping in v8 (Node.js JavaScript engine). Use of try-catch causes runtime de-optimizations resulting in unnecessarily slow execution – particularly for hot code paths. This means if we adopt throwing and try/catch all over a codebase it could significantly affect performance.

## **When try-catch may be useful**

Having said that, there are cases where try-catch could be necessary.

Typically if our app is converting a JSON string into a JavaScript object, that data would have arrived from an external source.

No matter what the external source we should consider the input as untrusted (even if it’s our own database, since it’s possible that some other part of the system allowed unchecked user input into the database.)

### **Parsing JSON**

Unfortunately, the native JSON.parse will throw when given invalid JSON. Yet the malformed JSON isn’t necessarily a programmer error it may be the result of malicious user manipulation, a problem in another part of the stack (e.g. the UI), or simply corrupted data, perhaps due to some hardware fault.

Unless we know for certain our JSON is valid, we need to try-catch JSON.parse to avoid unwanted crashes:

try {
  JSON.parse(userJson);
} catch (e) {
  console.warn('Bad user input', userJson, e);
}

It would be trivial to execute a Denial of Service attack on a Node.js process that doesn’t try-catch JSON.parse – simply keep sending invalid JSON.

### **Third-party Code**

Similarly if we’re using a module that throws unnecessarily we may have to use a try-catch. The mustache module, for example, has a policy of throwing on template parser errors. This seems okay because it’s responding to incorrect developer input. Except that it’s not critical to the system that the template renders. Should we really have to crash the server because someone made a typo in a template? In this case we’d need to wrap certain mustache calls in a try-catch (or switch to a template module that doesn’t throw).

### **Syntax Support Detection**

Another place where try-catch may be useful as we move into the brave new world of EcmaScript 6 is feature detecting syntax.

EcmaScript 6 is the first update to JavaScript that actually adds new syntax – there’s no way to polyfill the behaviour.

Say we wanted to detect the support of ES6 let:

var letSupport;
try {
  Function('let foo = 1;')();
  letSupport = true;
} catch (e) { }

var mod = letSupport ?
  require('es6mod') :
  require('transpiledMod');

There may however be better (as yet unknown) ways than using try-catch.

## **Isolating try-catch**

If a try-catch absolutely must be used, isolating the try-catch in a separate function and calling it from the original function confines de-optimizations to the purpose built function.

function parse(json) {
  var err;
  try { json = JSON.parse(json); } catch (e) { err = e; }
  if (err) {return err;}
  return json;
}


function doThings(json) {
  //doing things...
  json = parse(json); //doThings wont be affected by try-catch in parse
  //doing things...
}
## **Throwing in promise callbacks** Promises automatically catch exceptions. For instance instead of calling `reject` we _could _`throw`:  
function asyncOp(input) {
  return new Promise(function (resolve) {
    if (!Array.isArray(input)) {
      throw Error('input needs to be an array');
    }
  });
}

asyncOp('oops')
  .catch(function (err) {
    //handle the error

  });
However, let’s not forget that `throw` is synchronous, so the second `throw` in the following code would _not_ be caught:
function asyncOp(input) {
  return new Promise(function (resolve) {
    if (!Array.isArray(input)) {
      return throw Error('input needs to be an array');
    }

    doAsyncThing({input: input}, function (err, val) {
      if (err) { throw err; }
      resolve(val);
    });
  });
}
Calling `reject` is advised over `throw`ing. The `reject` function will trigger the `catch` callback in both synchronous and asynchronous contexts.

When we call then on a promise, a new promise is returned.

If the callback passed to then contains a throw the promise returned from then will trigger its catch callback if one is assigned.

Like so:
asyncOp
  .then(function () {
    throw Error('oh no');
  })
  .catch (function (err) {
    //handle it
  });

Again this won’t work in asynchronous scenarios:
asyncOp
  .then(function () {
    setImmediate(function () {
      throw Error('oh no'); //causes an unhandled exception
    });
  })
  .catch(function (err) {
    //wont trigger
  });

As with calling reject in the Promise instantiation callback it’s advisable to stay away from throw in promises and instead be explicit about error propagation in both synchronous and asynchronous context:

Asynchronous example:
asyncOp
  .then(function () {
    var defer = Promise.defer();
    setImmediate(function () {
      defer.reject(Error('oh no'));
    });
    return defer.promise;
  })
  .catch(function (err) {
    //will trigger
  });
Nesting asynchronous actions in Promise handlers isn’t a good pattern however. Prefer to encapsulate the asynchronous operations in separate promises, pass them to `Promise.all`:
Promise
  .all([asyncOp, asyncOpWithError])
  .catch(function (err) { /* will trigger */  });
An alternative to throwing synchronous errors in `then` callbacks is to generate a (synchronous) promise that triggers an error state with `Promise.reject`:
asyncOp
  .then(function () {
    return Promise.reject(Error('oh no'));
  })
  .catch(function (err) {
    //will trigger
  });

Since promises catch exceptions, we can use the Promise.reject methods counterpart, Promise.resolve, in place of try-catch when try-catch would usually be a necessity (e.g. with JSON.parse):

Promise.resolve('{"badJSON":oops}')
  .then(JSON.parse)
  .catch(function (err) {
    //JSON is bad.
  })

But there is a caveat. Promises catch all exceptions. So bear in mind registering a catch method on a promise will prevent the process from dying even when syntax and other native errors occur inside a promise context.These will probably be ignored in our catch handlers as the JavaScript engine typically (and rightly) causes a crash when these errors occur. So this means, native errors in a promise context can and do lead to silent failure.

We can handle this by detecting native errors and re-throwing them:
function rethrowNativeError(err) {
  var native = err instanceof TypeError ||
    err instanceof SyntaxError ||
    err instanceof EvalError ||
    err instanceof RangeError ||
    err instanceof ReferenceError;

  if (native) { throw err; }
}


asyncOp
  .then(function () {
    throw Error('manual')
  })
  .catch(function (err) {
    rethrowNativeError(err);
    //handle the manual error
  })
  .then(function () {
    undefined.badness();
  })
  .catch(function (err) {
    rethrowNativeError(err);
    //app will have thrown by now
  });

It’s not ideal but it can prevent some serious bugs and lost development time.

A nice approach to this would be to create a new constructor that inherits from the Promise constructor (call it Promise2 for example) and then implicitly rethrow native errors.

Unfortunately this can’t be done, native ES6 Promises cannot be inherited from.

## **What about try-catch-finally?**

Using the finally block can mitigate clean-up issues, however it’s not discussed in detail here because we encourage the use of alternatives to try-catch for reasons presented.

## **What about the** **uncaught exception** **event?** Uncaught exceptions can be caught like so:
process.on('uncaughtException', function () {
  //recover?
});

Beyond enhanced logging this is an ill-advised practice. Any time uncaughtException is used, there should be a process.exit inside the event handler function.

An unhandled exception means your application – and by extension Node.js itself – is in an undefined state. Blindly resuming means anything could happen.

## **What about domains?**

Since Node.js 0.8 the core domains module has allowed for an asynchronous try-catch-like mechanism.

The idea is to group code into “domains” and catch unhandled errors. Sounds good, but we still have the same issues as resuming execution after uncaughtException, just compartmentalized.

This is why domains are designed to be used alongside the cluster module. Each child process is housed inside a domain, when an error is caught the child process is restarted.

Until Node.js 0.12 (and io.js), both domains and cluster were experimental (and in the cluster module’s case, actually kind of broken).

This is an area where development and deployment converge.

The typical approach is to use process management tools and error logs – particularly in the absence of reliable clustering in core. This way some of the same deployment tools used for scaling horizontally can be used for scaling vertically.

We like to use nginx and nscale as part of our devops tooling instead of domains and cluster, choosing to make process management more of a declarative configuration affair rather than a programmatic one.

Nevertheless, now that cluster and domainhave moved to “unstable”, (which means the implementation is solid, but the API may change), we may see more usage of these in production.

## **Conclusion**

To summarise, it’s not that throwing and try-catch are inherently dangerous, but as a pattern used in an asynchronous, stateful imperative language they do make it too easy to inject instability into a process. It’s much safer to simply restart the process than to try and continue.

Endeavor to keep throwing to a minimum. Reserve it for scenarios where the intent is to explicitly exit the process, and prefer to delegate this decision to application level when possible.

If the purpose is to propagate an error, send an Error object via callback, emitter, promise. For synchronous functions, returning an Error is an option, but consider other approaches such as using typically asynchronous constructs like callbacks or promises for synchronous operations or creating an API contract that houses errors and values in a united object. Most importantly, pick an option and be consistent.

Endeavor to avoid try-catch, if it must be used (because something else is throwing) isolate it in a separate function.

That’s all for this time, hope you enjoyed it.

Let us know in the comments if you found it worth the read, or if you vehemently disagree I’d love to open a dialogue!

The next tip is titled ‘Reproduce Core Callback Signatures’, see you then!

Subscribe to this blog to be notified as soon as follow-on posts are published.

_Want to work for nearForm? We’re hiring._ * * *

Email hello@nearform.com

Twitter @nearform

Phone +353-1-514 3545

Check out nearForm at www.nearform.com.

* * * [1]: https://www.nearform.com/blog/2015/02/node-js-develop-debugging-techniques/ [2]: https://www.nearform.com/blog/2015/03/coding-with-nodejs/ [3]: https://www.nearform.com/blog/2015/11/10-tips-coding-node-js-4-reproduce-core-callback-signatures/ [4]: https://www.nearform.com/img/blog/2015/06/throw-design-2.png [5]: https://www.nearform.com/img/blog/2015/06/throw1.png

Subscribe to our monthly newsletter!
join the discussion