Errors in synchronous js code are pretty straightforward and not unlike errors or exceptions in other languages. They can be thrown and caught, and when they are created they get a stack trace.
var fnInner = function() {
throw new Error("busted");
};
var fnOuter = function() {
fnInner();
};
fnOuter();
# Result
/Volumes/dev/api/scratch.js:2
throw new Error("busted");
^
Error: busted
at fnInner (/Volumes/dev/api/scratch.js:2:11)
at fnOuter (/Volumes/dev/api/scratch.js:6:5)
at Object.<anonymous> (/Volumes/dev/api/scratch.js:9:1)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
But when you write asynchronous code, the bread crumbs leading to your error get lost.
var fnOuter = function() {
process.nextTick(fnInner);
};
fnOuter();
# Result
/Volumes/dev/api/scratch.js:2
throw new Error("busted");
^
Error: busted
at fnInner (/Volumes/dev/api/scratch.js:2:11)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
We don't see fnOuter in our trace anymore. What happened? process.nextTick registered our funciton with node's event loop. The stack that passed the function is not the stack the function will run in. The event loop calls the function, eventually, and that's the stack you get.
Async functions register your callback with the event loop, so you'll see similar results. Lets try with fs.readFile.
var fs = require("fs");
var processHelper = function(err, data) {
throw new Error("busted");
};
var processFile = function(filename) {
fs.readFile(filename, processHelper);
};
processFile(__filename);
# Result
/Volumes/dev/api/scratch.js:4
throw new Error("busted");
^
Error: busted
at processHelper (/Volumes/dev/api/scratch.js:4:11)
at fs.readFile (fs.js:176:14)
at Object.oncomplete (fs.js:297:15)
Again, we don't see the outer function (now processFile
) in the trace. An even bigger problem: our program is crashing before processFile gets
a chance to handle the error. The error just bubbles up to the top of the event loop and then... crash. Let's fix this by using callbacks to handle our errors.
var fs = require("fs");
var processHelper = function(data, callback) {
callback(new Error("busted"));
};
var processFile = function(filename, callback) {
fs.readFile(filename, function(err, data) {
if (err) {
return callback(err);
}
processHelper(data, callback);
});
};
processFile(__filename, function(err, result) {
if (err) {
console.log("Bubbled:", err.stack);
return;
}
});
Bubbled: Error: busted
at processHelper (/Volumes/dev/api/scratch.js:4:8)
at processFile (/Volumes/dev/api/scratch.js:13:9)
at fs.readFile (fs.js:176:14)
at Object.oncomplete (fs.js:297:15)
Great! We've got a meaningfull trace, with fnOuter, and our program didn't crash. Problem is, our program is starting to look ugly. "Callback hell" has started. Thankfully, we've got some nice libraries like async.js and promises that help with callback hell.
Let's try this with promises.