2

I working on a framework. I want to define a CompositeError class which means that it can contain other related errors...

var err1 = new Error("msg1");
var err2 = new TypeError("msg2");
var err3 = new Error("msg3");

var err = new CompositeError({
    errors: [err1, err2, err3]
});

Now by v8 I have a stack something like this by a simple error:

var stackString = [
    "Error",
    "   at module.exports.extend.init (http://example.com/example.js:75:31)",
    "   at new Descendant (http://example.com/example.js:11:27)",
    "   at custom (http://example.com/spec/example.spec.js:222:23)",
    "   at Object.<anonymous> (http://example.com/spec/example.spec.js:224:13)",
    "   at http://example.com/spec/example.spec.js:10:20"
].join("\n");

There are several Stack parser libraries, for example: https://github.com/stacktracejs/stacktrace.js

Now what do you think, how can I merge the stacks of the 4 Error instances into one and stay parser compatible?

(Note that we are talking about asynchronous code, so unlike synchronous code, the 4 errors can have completely different stacks.)

conclusion

I decided not to be stack parser compatible by composite errors. I simply write the path of the property e.g. x.y.z and the stack of the sub Error.

try {
    try {
        throw new df.UserError("Something really bad happened.");
    }
    catch (cause) {
        throw new df.CompositeError({
            message: "Something really bad caused this.",
            myCause: cause
        });
    }
catch (err) {
    console.log(err.toString());
    // CompositeError Something really bad caused this.

    console.log(err.stack);
    // prints the stack, something like:
    /*
        CompositeError Something really bad caused this.
            at null.<anonymous> (/README.md:71:11)
            ...
        caused by <myCause> UserError Something really bad happened.
            at null.<anonymous> (/README.md:68:9)
            ...
    */
}

This is somewhat similar to java and other languages which support nested Errors.

It can be easily nested like this:

new df.CompositeError({
    message: "A complex composite error.",
    x: new df.CompositeError({
        message: "Nested error x.",
        y: new df.CompositeError({
            message: "Nested error x.y",
            z: new Error("Deeply nested error x.y.z")
        })
    })
});

2 Answers 2

3

You're in luck, there's now a native AggregateError which does exactly what you need:

const err1 = new Error("msg1");
const err2 = new TypeError("msg2");
const err3 = new Error("msg3");

const err = new AggregateError([err1, err2, err3]);
Sign up to request clarification or add additional context in comments.

Comments

1

I is hard to find anything about the stack.

I think I could do something like this:

var stackString = [
    "CompositeError msg - composed of #1:Error msg1 #2:TypeError msg2 #3:Error msg3",
    "   at module.exports.extend.init (http://example.com/example.js:75:31)",
    "   at new Descendant (http://example.com/example.js:11:27)",
    "   at Error (#1:0:0)",
    "   at custom (http://example.com/spec/example.spec.js:222:23)",
    "   at Object.<anonymous> (http://example.com/spec/example.spec.js:224:13)",
    "   at TypeError (#2:0:0)",
    "   at http://example.com/spec/example.spec.js:10:20",
    ...
].join("\n");

The problem with the whole Composite stack thing, that there are differences in environments. Not just stack format differences (which can be managed with different parsers), but stack creation differences as well...

The stack property is set to undefined when the error is constructed, and gets the trace information when the error is raised. If an error is raised multiple times, the stack property is updated each time the error is raised.

https://msdn.microsoft.com/en-us/library/ie/hh699850%28v=vs.94%29.aspx

For example by nodejs the stack is created by the instantiation of the Error, while in MSIE it is created by throwing the Error instance.

var err = new Error(msg); // 1st line

//...

console.log(err.stack);
// by node.js this prints the stack generated by the 1st line
// by msie this prints nothing (according to the documentation, I did not try it out yet)

try {
    throw err; // Nth line
} catch (err){
    console.log(err.stack); 
    // by node.js this prints the stack generated by the 1st line
    // by msie this prints the stack generated by the Nth line (according to the documentation)
}

So I have to do something like the following if I want to stay in the business:

var Error = extend(NativeError, {
    id: undefined,
    name: "Error",
    message: "",
    configure: Object.prototype.configure,
    isOptions: Object.prototype.isOptions,
    init: function (options, preprocessor) {
        this.id = id();
        if (typeof (options) == "string")
            options = {message: options};
        this.configure(options, preprocessor);

        var nativeError;
        try {
            throw new NativeError();
        } catch (caughtError){
            nativeError = caughtError;
        }

        var error = this;
        var stack;
        NativeObject.defineProperty(this, "stack", {
            enumerable: true,
            get: function () {
                if (stack === undefined) {
                    stack = "";
                    stack += error.name + " " + error.message + "\n";
                    stack += Stack.instance(nativeError);
                    delete(nativeError);
                }
                return stack;
            }
        });
    }
}, {
    instance: Object.instance,
    extend: Object.extend
});

Now I need parsing because without that every custom Error stack would contain the instantiation of my custom Error instances and the call of the init() as you can see here:

var stackString = [
    "Error",
    "   at module.exports.extend.init (http://example.com/example.js:75:31)",
    "   at new Descendant (http://example.com/example.js:11:27)",
    ...
].join("\n");

By subclasses the list would contain the init() of the subclass too (if it overrides and after that calls the original init).

edit:

I ended up using this format

CompositeError msg
    at ...
    ...
caused by <p> CompositeError msg2
    at ...
    ...
caused by <p.x> CustomErrror1 msg3
    at ...
    ...
caused by <q> CustomError2 msg4
    at ...
    ...

by

throw new CompositeError({
    message: msg,
    p: new CompositeError({
        message: msg2,
        x: new CustomError1({
            message: msg3
        })
    }),
    q: new CustomError2({
        message: msg4
    })
});

Maybe caused by is not the best term to describe this relation, probably in association or something like that would be better, but I think it does not really matter what the separator string is. If I remember well this caused by is from java composite exceptions.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.