Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I debug promise-based code in node?

I am using Cujo's great When library to provide a Promises/A+ implementation for my Node project, although this question is not node-specific.

Generally, When's great: it lets me write more maintainable, readable code.

However, when my callbacks fail unexpectedly (accessing a property of a null variable, etc), the exceptions are effectively swallowed by When, as seems to be specified by the Promises/A+ spec. Unfortunately, this means I don't get any feedback about the error (other than the callback stops executing at that point). No error type or message, no line number.

To illustrate:

// hypothetical asynchronous database query
database.query(queryDetails).then(function(result) {

  var silly = 3.141592654;
  silly(); // TypeError: number is not a function!

  process(result); // this code is silently never executed

});

I can think of a handful of (unacceptable) ways to tackle this:

  • providing failure callbacks for every then call (to dump the reason/exception to the console)
  • wrapping all callback bodies in try-catches
  • littering the codebase with "landmark logs" ala console.log('I got here 123')

Am I just doing it wrong? Surely I'm not alone in finding the debuggability of promises-based code poor. Is there an obvious solution I'm missing?

like image 541
aaaidan Avatar asked Jan 08 '14 08:01

aaaidan


2 Answers

Here is how I detect when a Promise has been rejected on Node but not caught:

if (typeof process === 'object') {
    process.on('unhandledRejection', (error, promise) => {
        console.error("== Node detected an unhandled rejection! ==");
        console.error(error.stack);
    });
}

In addition to that, you could use this monkey wrapper to provide long stack traces for Node's ES6 Promises. It produces similar output to Q's longStackSupport. I would not recommend it for use outside of development code, due to performance concerns. (It's working for me in Node v4.4.1. I have not yet tested it in Chrome or Firefox.)

like image 80
joeytwiddle Avatar answered Sep 21 '22 17:09

joeytwiddle


Update Sep 2016: NodeJS 6.6.0+ and 7.0+ will warn automatically on unhandled rejections. Run node with --trace-warnings to get reasonable stack traces. Still not as good as what bluebird gives you but a lot better than the situation before.


Ok, so summing up the information from the comments and add some.

  • There is nothing in the Promises/A+ specification that dictates how to deal with this problem. The specification is about the minimal requirements for good interop between different promise libraries - so one promise library can consume promises created in another and vice versa.
  • Several libraries solve this problem by including a .done method that explicitly declares the chain has ended, this causes uncaught rejections to be thrown. Libraries like When and Q solve the problem this way. For example if your .then after .query was a .done you'd get a long stack trace.
  • Newer, less naive implementations of promises like Bluebird solve this problem by automatically figuring out possibly uncaught rejections and logging them out for you loudly. They also give you a hook. When has experimental support for this using the monitor.

As such:

 require('when/monitor/console'); // when will now log async rejections used with
                                  // `then` , this is experimental in when.

With bluebird

Promise.longStackTraces(); // Bluebird always logs async rejections but with this 
                           // option it will stitch the asynchronous context stack
                           // for you in your methods.
  • ES6 promises' behavior is unspecified on this. There is no explicit requirements on the native implementations with regards to this. However, I know vendors are working on it and I'd expect engines to start figuring this out even in native implementations.
like image 41
Benjamin Gruenbaum Avatar answered Sep 21 '22 17:09

Benjamin Gruenbaum