Arrow function implicit return, Mocha done(err) and Node Error type non-enumerable

One sunny day, I have this setup in my mocha test: [js] before((done) => { task1().then(done).catch((reason) => console.log(reason)); }); // test cases function task1() { return subtask1().then((result) => { global = result; console.log(‘assignment done’); }); } [/js] This all works fine. So I removed the print in task1(), that is: [js] function task1() { return subtask1().then((result) => global = result); } [/js] And it broke. Imagine the look on my face…

Just removing the console.log statement breaks it. Stably reproducible. Never execute beyond before(), with nonsensical output like:

TypeError: Converting circular structure to JSON

Where did I stringify anything anyway?

I figured out that removing the print and changing arrow function statements will change the return value, from undefined to global (side-effect of assignment). So the value passed to next onFulfilled has changed. I also figured out that done, when called with non-null argument, indicates erroneous condition and stops mocha execution. To verify, I took a peek at the reject reason: [js] console.log(reason, typeof reason, Object.keys(reason), util.inspect(reason, true, 7, true)); [/js] You see my desperation.

And I get the following result: [js] TypeError: Converting circular structure to JSON object [] [/js] What? Why object with no keys? And where’s my util.inspect printout? Empty obj?

Eventually I decided to look at the reason obj in debugger. I found the last piece of the puzzle there. I sort of gave it away a bit in the post title: [js] TypeError: Converting circular structure to JSON message: "Converting circular structure to JSON" stack: undefined proto: TypeError [/js] So, Error and its subclasses’ properties, like message and stack, are non-enumerable in Node. That’s why we see [] as output of Object.keys(). See http://www.bennadel.com/blog/2874-error-object-properties-are-not-iterable-enumrable-in-node-js.htm

Seems I’m going down the right path - assignment’s side-effect returned, passed to done(), done() is about to stringify and say something but TypeError: circular is thrown so done is rejected with it and went to catch block. Now, where exactly is this stringify?

Answer is, of course, in Mocha source: [js] function callFnAsync(fn) { fn.call(ctx, function(err) { if (err instanceof Error || toString.call(err) === ‘[object Error]’) { return done(err); } if (err) { if (Object.prototype.toString.call(err) === ‘[object Object]’) { return done(new Error(‘done() invoked with non-Error: ‘ + JSON.stringify(err))); } return done(new Error(‘done() invoked with non-Error: ‘ + err)); } done(); }); } [/js] Great. No quantum physics involved. Just a another innocent bug.

Written on July 2, 2016