36

Is there a standard / best practice way to add a cause of an exception in javascript. In java you might do this:

Throwable t = new Exception("whatever");
t.addCause(previouslyCaughtException);
throw t;

When the resulting exception is printed, it'll give you a nice trace that includes causes. Is there any good way to do this in javascript or do I have to roll my own?

B T
  • 57,525
  • 34
  • 189
  • 207

7 Answers7

10

For now (until there's a better answer), this is what I've done:

...
} catch(e) {
  throw new Error("My error message, caused by: "+e.stack+"\n ------The above causes:-----")
}

The way I'm printing exceptions makes it nice and clean looking:

console.log(e.stack)

prints something like this:

My error message: SomeError
<some line>
<more lines>
------The above causes:-----
<some line>
<more lines>

The line might be better if it said "causes" because the stack trace of the exception causing the error is printed first.

B T
  • 57,525
  • 34
  • 189
  • 207
  • 3
    I would suggest adding the original error's message. Thanks for the hint nontheless! – ooxi Sep 11 '14 at 08:10
  • 1
    Nice, small and simple - love it! My version: ```module.exports = function (msg, causeE) { return new Error(`${causeE.message}: ${causeE.stack}\n ----------- CAUSES -----------\n${msg}`) } ``` – bungee May 18 '19 at 08:14
8

If you are using Node.js you can use VError

Joyent also have a page discussing best practices for error handling in Node.js https://www.joyent.com/developers/node/design/errors

The only thing is I don't like how Joyent's implementation of VError falls over if you pass in a null or undefined parameters. This is particularly important when you are handling errors as it'll just mask the root cause of the problem. I've forked their VError so it will not fail with null or undefined parameters. https://github.com/naddison36/node-verror

naddison
  • 603
  • 1
  • 8
  • 11
7

In prod, we use TraceError

Usage

import TraceError from 'trace-error';

global.TraceError = TraceError; // expose globally (optional)

throw new TraceError('Could not set status', srcError, ...otherErrors);

Output

Mathew Kurian
  • 5,949
  • 5
  • 46
  • 73
  • Ooo, I like this. You should make a github repo and npm package for this. I can see a few useful improvements to this (like eliminating duplicate stack-trace lines between otherErrors exceptions and getting rid of the inner TraceError part of the stack). I'll have to give this a try at some point! – B T Mar 14 '16 at 21:16
  • 1
    @BT Glad you like it :) I have put it out as a npm module and git repo as per your request. I have updated the answer as well. Let me know how I can improve. Perhaps a PR? I am not sure what you mean by `duplicate stack-trace lines` – Mathew Kurian Mar 15 '16 at 07:17
  • By "duplicate stack-trace lines" I mean that in most situations where you're catching one exception and then throwing a related exception, the stack trace of the original exception has many of the same lines as the newly created exception, and the output could be simplified to take advantage of that. – B T Mar 16 '16 at 00:03
  • @BT One issue I see is; if you use `setTimeout` or `process.nextTick`, then you will not be able to see that via. the stack trace if we remove similar traces. Any suggestions? – Mathew Kurian Mar 19 '16 at 14:24
  • 1
    I'm not entirely sure I understand what you mean, but I was suggesting *combining* the similar parts of the stack trace, rather than removing both similar parts. So you'd still get all the stack information, but you'd just remove the duplicate parts. – B T Mar 19 '16 at 19:33
  • 1
    Honestly I haven't had a chance to try this out yet, but I'm accepting it as the answer cause it has all the right ideas, and you went through the trouble to release it as a module we can all benefit from! – B T Apr 05 '16 at 00:56
  • It looks like the TraceError project was not updated in 3 years, maybe it's not the best thing to use at this point in time. – Ido Ran Nov 25 '18 at 13:31
7

In NodeJS this is now possible thanks to v8 version 9.3 And it is already introduced in NodeJS version 16.9.0

https://v8.dev/features/error-cause https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#16.9.0

try {
  aFunctionThatWillThrow()
} catch (err) {
  throw new Error('A function that will throw did throw!', { cause: err });
}
Itay Wolfish
  • 507
  • 3
  • 9
2

tl;dr This is not a solution, just a helper till ECMA Script adopts some standard.

EDIT: I wrapped this answer into the chainable-error npm package.

Well this is a kind of a difficult topic. The reason is, there is no definition about the stack trace in the ECMA Script definition (not even in ES9 / ES2019)). So some engines implement their own idea of an stack trace and its representation.

Many of them have implemented the Error.prototype.stack property which is a string representation of the stack trace. Since this is not defined you can not rely on the string format. Luckily the V8 engine is quite common (Google Chrome and NodeJS) which gives us a chance to at least try to.

A good thing about the V8 (and the applications using it) is that the Stack trace has a common format:

/path/to/file/script.js:11
        throw new Error("Some new Message", e);
        ^

Error: Some new Message
    at testOtherFnc (/path/to/file/script.js:69:15)
    at Object.<anonymous> (/path/to/file/script.js:73:1)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at startup (internal/bootstrap/node.js:285:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)

...and the stack trace is not parsed and styled in the console.

Which gives us a good opportunity to chain them (or at least change the output generated of the error).

A quite easy way to do this would be something like this:

let ff = v => JSON.stringify(v, undefined, 4);
const formatForOutput = v => {
    try {
        return ff(v).replace(/\n/g, '\n    ');
    } catch (e) {
        return "" + v;
    }
};

const chainErrors = exporting.chainErrors = (e1, e2) => {
    if (e1 instanceof Error)
        e2.stack += '\nCaused by: ' + e1.stack;
    else
        e2.stack += '\nWas caused by throwing:\n    ' + formatForOutput(e1);

    return e2;
}

Which you could use like this:

function someErrorThrowingFunction() {
    throw new Error("Some Message");
}

function testOtherFnc() {
    try {
        someErrorThrowingFunction();
    } catch (e) {
        throw chainErrors(e, new Error("Some new Message"));
    }
}

Which produces:

/path/to/file/script.js:11
        throw new Error("Some new Message", e);
        ^

Error: Some new Message
    at testOtherFnc (/path/to/file/script.js:11:15)
    at Object.<anonymous> (/path/to/file/script.js:15:1)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at startup (internal/bootstrap/node.js:285:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:739:3)
Caused by: Error: Some Message
    at someErrorThrowingFunction (/path/to/file/script.js:4:11)
    at testOtherFnc (/path/to/file/script.js:9:9)
    at Object.<anonymous> (/path/to/file/script.js:15:1)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
    at startup (internal/bootstrap/node.js:285:19)

Which is pretty similar to the stack trace generated by Java. There are three problems with this.

The first problem is the duplication of the call sites, which is solveable but complicated.

The second is that the output generated is engine dependent, this attempt works quite well for V8 but is not usable for Firefox for example, since Firefox not just uses another style but they also parse and style the error massage which prevents us from chaining it like this.

The third problem is usability. This is a little bit clunky, you have to remember this function and you need to keep track if you are in the right engine. Another way to do this would be something like this:

const Error = (() => {
    const glob = (() => { try { return window; } catch (e) { return global; } })();

    const isErrorExtensible = (() => {
        try {
            // making sure this is an js engine which creates "extensible" error stacks (i.e. not firefox)
            const stack = (new glob.Error('Test String')).stack;
            return stack.slice(0, 26) == 'Error: Test String\n    at ';
        } catch (e) { return false; }
    })();

    const OriginalError = glob.Error;

    if (isErrorExtensible) {
        let ff = v => JSON.stringify(v, undefined, 4);
        const formatForOutput = v => {
            try {
                return ff(v).replace(/\n/g, '\n    ');
            } catch (e) {
                return "" + v;
            }
        };

        const chainErrors = (e1, e2) => {
            if (e1 instanceof OriginalError)
                e2.stack += '\nCaused by: ' + e1.stack;
            else
                e2.stack += '\nWas caused by throwing:\n    ' + formatForOutput(e1);

            return e2;
        }

        class Error extends OriginalError {
            constructor(msg, chained) {
                super(msg);

                if (arguments.length > 1)
                    chainErrors(chained, this);
            }
        }

        return Error;
    } else
        return OriginalError; // returning the original if we can't chain it
})();

And then you could do it just like in Java:

function someErrorThrowingFunction() {
    throw new Error("Some Message");
}

function testOtherFnc() {
    try {
        someErrorThrowingFunction();
    } catch (e) {
        throw new Error("Some new Message", e);
    }
}

testOtherFnc();

Even though the second version brings some (other) problems with it might be the "easier" one, since you do not need to change your code even when the engine does not support the chaining because you could give a function (the Error constructor) as many parameters as you want.

Either way, hopefully this will be something for ES2020.

Feirell
  • 719
  • 9
  • 26
1

You can chain errors objects Error doing a concat of stack and message.

var console = {
    log: function(s) {
      document.getElementById("console").innerHTML += s + "<br/>"
    }
  }

var error1=new Error("This is error 1");
console.log("Message: ".concat( error1.message ));
console.log("Stack<br>".concat(error1.stack) );

var error2=new Error("This is error 2");
console.log("Message: ".concat( error2.message) );
console.log("Stack<br>".concat( error2.stack) );

var error3=new Error("This is error 3");
error3.stack=error3.stack.concat(error2.stack).concat(error1.stack)
console.log("Message: ".concat(error3.message));
console.log("Stack<br>".concat(error3.stack));
<div id="console" />
loretoparisi
  • 15,724
  • 11
  • 102
  • 146
0

As of late 2021, all the main browsers have implemented the new cause property of Error that can be used for exception chaining.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause

Example usage from that article:

try {
  connectToDatabase();
} catch (err) {
  throw new Error("Connecting to database failed.", { cause: err });
}
Maian
  • 528
  • 2
  • 5
  • 11