Tutorial: Examples

Examples

better-custom-error

Some examples.

Imporant: creating two errors with the same name will create two different errors references anyway! Export them and import where needed or make your errors global.

Native solution doesn't work as expected

const MyError = createError("MyError");
const MyOtherError = createError("MyOtherError", MyError);

const error = new MyOtherError();
console.log(error instanceof MyError); // true

// vs native:

class MyError extends Error {}
class MyOtherError extends Error {}

const error = new MyOtherError();
console.log(error instanceof MyError); // false

Custom error details object simplifies passing custom data

throw new MyCustomError("Cannot do stuff", { debugStats: databaseEngine.stats() });

// vs native:

const error = new Error("Cannot do stuff");
error.details = { debugStats: databaseEngine.stats() };
throw error;

Create instance basing on another instance

try {
    db.query("SELECT ..."); // it throws QueryError for example with a message
}
catch (error) {
    throw new DatabaseError(error, { debugStats: db.stats() }); // this will inherit message and add stats
}

Pass data in any order, pass null or undefined for simplicity

let customData = LOGGING_LEVEL === "ALL" ? { time: db.time(), query: query } : null;
throw new DatabaseError(customData, "Query cannot run");

Advanced usage and names property

This shows advanced usage of custom errors. I tried to show most features in tiny part of code, so it's a little messy. In this example TypeError will be thrown from getProduct, but in renderProductPage it will be caught and wrapped by DatabaseError. DatabaseError inherits from InternalServerError and this is finally used for deciding which error page to show.

e.names will contain array which describe errors hierarchy - DatabaseError was created from TypeError instance, which inherits from Error and DatabaseError itself inherits from InternalServerError which inherits from Error).

[
    "DatabaseError",
    ["TypeError", "Error"],
    "InternalServerError",
    "Error",
]

For logging purposes we're casting it to string and still be able to see hierarchy, we will get:

"(DatabaseError,(TypeError,Error),InternalServerError,Error)"

The code:

const NotFoundError = createError("NotFoundError");
const InternalServerError = createError("InternalServerError");
const DatabaseError = createError("DatabaseError", InternalServerError);

const getProduct = id => {
    if (!id) {
        throw new TypeError("Product id is missing");
    }
    const data = db.query(/* query here */);
    if (!data) {
        throw new NotFoundError("Product cannot be found", { id });
    }
    return data;
};

const renderProductPage = (id) => {
    try {
        const product = getProduct(id);
        return renderProduct(); // can throw RenderError which inherits from InternalServerError too
    }
    catch (error) {
        if (error instanceof NotFoundError) {
            throw error; // just re-throw
        }

        throw new DatabaseError(error);
    }
}

try {
    const render = router.getRenderMethodForUrl(url); // let's assume we are on product page url
    const html = render(); // calls renderProductPage
    res.send(html);
}
catch (e) {
    // text-based log service
    loggingService.log(e.name, e.message, JSON.stringify(e.details), String(e.names), e.stack);

    if (e instanceof NotFoundError) {
        res.send(router.renderError(404));
    }
    if (e instanceof InternalServerError) {
        res.send(router.renderError(500));
    }
}