Error handling in Node.js

Wojtek Gawronski (@afronski, afronski.pl) - 2014 © License: CC BY-ND 3.0 PL

Let it Crash

Let it Crash

Let it Crash?

Let it crash? Really?

let-it-crash !== let-it-fail-randomly

Supervision

Supervision Trees and Concept of Supervision

UNIX philosophy

"Worse is better"

Error Kernel

  • Static Data
  • Recomputable Dynamic State
  • Non-computable Dynamic State

Exception

What is this?

Types of Errors

  • Operational Errors
  • Programmer Errors

Operational Errors

run-time problems experienced by correctly-written programs

Programmer Errors

bugs in the program

How to provide an error?

You should not do both!

Case Studies

catch-all


process.on("uncaughtException", function(err) {
  // ALL YOUR ERRORS ARE BELONG TO US
  // YOU CANNOT STOP ME NOW!
});
            

process.on("uncaughtException", function(err) {
  syslog.serializeError(err);

  // ANOTHER ONE BITES THE DUST!
  process.exit(1);
});
            

asynchronous-throw


http.get("http://ipinfo.io/8.8.8.8", function (response) {
  response.pipe(concat(function (content) {
    var object = JSON.parse(content);
    console.log(object.loc);
  }));
});
            

error-handling-in-streams


response.pipe(saveToS3).pipe(temporarySaveToDisk);
            

// Better, but still bad:
response.pipe(saveToS3);
response.pipe(temporarySaveToDisk);
            

// Best (errorHandler is a PassThrough stream implementation):
response.pipe(errorHandler)
        .pipe(saveToS3);

response.pipe(temporarySaveToDisk)
        .on("error", function () { /*...*/ })
            

ignoring-errors


socket.on("connection", function (connection) { /*...*/ });
socket.on("disconnection", function () { /*...*/ });
            

socket.on("connection", function (connection) { /*...*/ });
socket.on("disconnection", function () { /*...*/ });

socket.on("error", function (err) { /*...*/ });
            

sound-of-silence


try {
  unsafeOperationWhichEventuallyThrow(Math.rand());
} catch(e) {
  // Oups!
}
            

try {
  unsafeOperationWhichEventuallyThrow(Math.rand());
} catch(exception) {
  syslog.reportError(level, exception);
  business_logic.restore();
  transaction.rollback();
}
            

if-err-antipattern


fs.stat(path, function (err, stat) {
  if (err) {
    return done(err);
  }

  fs.open(path, "r+", function (err, fd) { /* DEEPER... */ });
});
            

var handler = require("domain").create();

handler.on("error", done);

fs.stat(path, handler.intercept(function (stat) {
  fs.open(path, "r+", handler.intercept(function (fd) {
      // ...
  }));
}));
            

discard-my-resources


try {
  setInterval(importantLogic, 1000);
  itWillThrow();
} catch(e) {
  syslog.error(e);

  // OH YOU DIRTY, LITTLE BASTARD...
}
            

var timer;

try {
  timer = setInterval(importantLogic, 1000);
  itWillThrow();
} catch(e) {
  syslog.error(e);
  clearInterval(timer);
}
            

exception-as-a-logic-flow


for (i = 0; i < N; ++i) {
  for (j = i + 1; j < N; ++j) {
    if (specialCondition()) {
      throw "CONTINUE-WITH-FLOW";
    }
  }
}
            

for (i = 0; i < N; ++i) {
  for (j = i + 1; j < N; ++j) {
    if (specialCondition()) {
      return true;
    }
  }
}
            

design-hijacked-by-a-library


asynchronousOperationFromLibrary(arg, function(result, err) {
  // IT'S NOT AN ERROR, IT'S BROKEN CONVENTION
});
            

asynchronousOperationFromLibrary(arg, function(err, result) {
  // NOW IT'S BETTER
});
            

vapourous-details


try {
  /*...*/
} catch(original_err) {
  throw "My Error Happened!"
}
            

try {
  /*...*/
} catch(original_err) {
  throw new VError(original_err, 'Domain error: %s', argument);
}
            

wrong-abstraction

using wrong abstraction in wrong place


var domain = require("domain");

domain.create().run(function () {
  // WHY SO DOMAIN?
  JSON.parse("")
});
            

meh-i-have-promises


readFilePromise("config.json")
  .then(function (text) {
    return JSON.parse(text);
  }, function (err) {
    // DAMN YOU JSON.parse, NOT AGAIN!
    return defaultConfig;
  });
            

readFilePromise("config.json")
  .then(function (text) {
    return JSON.parse(text);
  })
  .then(null, function (err) {
    // Now it's okay.
    return defaultConfig;
  });
            

the-ultimate-stack-trace


Error: Fatal Error! Oh no, now what? :(
  at Error (<anonymous>)
  at node.js:902:3
            

# And logs collected in one place e.g. rsyslog:
2014-08-13T11:59:59.000Z APP2 as2.app.com
[FATAL] Syntax error in configuration: unexpected empty string.

2014-08-13T12:00:02.000Z APP1 as1.app.com
[WARN] Invalid attributes passed to important service: "null"

2014-08-13T12:00:02.000Z APP1 as1.app.com
[ERROR] Remote peer unavailable:
        URL: app2.app.com:8080/get_important_data?q=null
        Status code: 500
        Body: Fatal Error! Oh no, now what? :(
            

Thanks!

Picture credits:

References: