Skip to content

Instantly share code, notes, and snippets.

@aaronyo
Last active December 17, 2015 12:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronyo/5609406 to your computer and use it in GitHub Desktop.
Save aaronyo/5609406 to your computer and use it in GitHub Desktop.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment