Dan Levy's Avatar DanLevy.net

Quiz: Advanced JS Error Mastery

Are your exceptions truly exceptional?

Think you know JavaScript errors inside and out?

What does JSON.stringify(error) return?

const error = new Error('Oops');
console.log(JSON.stringify(error));

Think about enumerable properties on Error objects.

Error objects have non-enumerable properties (message, name, stack), so JSON.stringify() returns {}. This is a common gotcha when sending errors in API responses. Use JSON.stringify(error, Object.getOwnPropertyNames(error)) or create a plain object instead.

What’s the difference between these two?

const err = new Error('Test');
console.log(err);
console.log(JSON.stringify(err));

Consider how console.log handles objects vs JSON serialization.

console.log(err) shows the error with its message and stack trace because the console has special handling for Error objects. JSON.stringify(err) returns '{}' because Error properties aren’t enumerable. This difference trips up many developers debugging APIs.

What are the results of these checks?

class CustomError extends Error {}
const err = new CustomError('test');
console.log(err instanceof CustomError);
console.log(err instanceof Error);
console.log(err instanceof Object);

Remember the prototype chain in JavaScript inheritance.

All three return true. CustomError extends Error, which extends Object. The instanceof operator checks the entire prototype chain, so a CustomError instance is also an instance of Error and Object.

What happens with instanceof Error across iframes?

// In iframe:
const iframeError = new Error('test');
// In parent window:
console.log(iframeError instanceof Error);

Different contexts have different Error constructors.

instanceof can return false across different execution contexts (iframes, workers) because each context has its own Error constructor. Use Object.prototype.toString.call(obj) === '[object Error]' for reliable error detection across contexts.

What happens when you throw a string?

try {
throw "Oops!";
} catch (e) {
console.log(e instanceof Error);
console.log(typeof e);
}

JavaScript allows throwing any value, not just Error objects.

JavaScript allows throwing any value. Here, e instanceof Error is false and typeof e is "string". This can break error handling code that assumes all caught exceptions are Error objects. Always throw Error instances for better debugging.

What’s the value of err.name?

class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
const err = new CustomError('test');
console.log(err.name);

Look at what this.constructor.name evaluates to.

err.name is "CustomError" because this.constructor.name returns the class name. Setting this.name = this.constructor.name is a common pattern to ensure custom error classes display the correct name in stack traces and error messages.

What’s the output without setting name?

class MyError extends Error {
// No constructor or name setting
}
const err = new MyError('test');
console.log(err.name);

What does Error’s default name property contain?

Without explicitly setting this.name, the error inherits the default name property from the Error class, which is "Error". This is why custom error classes should always set this.name = this.constructor.name in their constructor.

What does wrapper.cause.message return?

const original = new Error('Original error');
const wrapper = new Error('Wrapper',
{ cause: original }
);
console.log(wrapper.cause.message);

Error.cause is a modern JavaScript feature for error chaining.

Error.cause (ES2022) allows chaining errors to preserve the original error context. wrapper.cause references the original error, so wrapper.cause.message returns "Original error". This is useful for wrapping lower-level errors with higher-level context.

What does Error.captureStackTrace do?

function createError(msg) {
const err = new Error(msg);
Error.captureStackTrace(err, createError);
return err;
}
const error = createError('test');

This is a V8-specific feature for cleaner stack traces.

Error.captureStackTrace (V8/Node.js) removes the specified function (createError) from the stack trace, making error factory functions invisible to end users. This creates cleaner stack traces that point to where the factory was called, not the factory itself.

What’s the error message?

function validate(value) {
if (!value) {
throw new Error(
`Value ${value} is invalid`
);
}
}
try {
validate(undefined);
} catch (e) {
console.log(e.message);
}

How does template literal interpolation handle undefined?

Template literals convert undefined to the string "undefined" during interpolation. The error message becomes "Value undefined is invalid". For cleaner messages, consider using value ?? 'null' or similar checks before interpolation.

What gets sent to the client?

// Express.js route
app.get('/api/data', (req, res) => {
const error = new Error('Database failed');
res.json({ error });
});

Remember how Error objects are serialized by JSON.stringify.

res.json() uses JSON.stringify() internally, so the Error object becomes {}. The client receives {"error":{}}. To fix this, use res.json({ error: error.message }) or res.json({ error: { message: error.message, name: error.name } }).

What can Promise.reject() accept?

Promise.reject('string').catch(e =>
console.log(typeof e)
);
Promise.reject({code: 404}).catch(e =>
console.log(e.code)
);
Promise.reject(42).catch(e =>
console.log(e)
);

Promise rejections work similar to throw statements.

Like throw, Promise.reject() accepts any value - strings, objects, numbers, etc. This prints "string", 404, and 42. Always check the type of caught values in promise chains, especially when dealing with third-party code that might reject with non-Error values.

How reliable are error.code and error.errno?

const fs = require('fs');
fs.readFile('missing.txt', (err, data) => {
if (err) {
console.log(err.code); // 'ENOENT'
console.log(err.errno); // -2
}
});

Consider different JavaScript environments and error types.

Properties like code and errno are environment-specific (Node.js in this case) and not part of the standard Error object. Browser errors won’t have these properties. Always check for their existence: if (err.code === 'ENOENT') rather than assuming they exist.

What do these checks return?

const fakeError = {
name: 'Error',
message: 'Fake error',
stack: 'fake stack'
};
console.log(fakeError instanceof Error);
console.log(Object.prototype.toString.call(
fakeError
) === '[object Error]');

One check looks at prototype chain, the other at internal slots.

instanceof Error returns false because the object wasn’t created by the Error constructor. Object.prototype.toString.call() also returns false (it returns '[object Object]') because it checks the internal [[Class]] slot. Both methods correctly identify this as a fake error object.

Quiz Score:

Congrats! Quiz completed.

Master the Art of Error Handling

From serialization gotchas to cross-context instanceof failures, these advanced concepts separate junior developers from seasoned damaged professionals.

Ready for more challenges? Check out our complete quiz collection for additional brain teasers on JavaScript, algorithms, and more!

Edit on GitHubGitHub