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.