DanLevy.net

测验:高级JS错误精通

你的异常真的异常吗?


你以为你完全掌握了 JavaScript 错误处理?

JSON.stringify(error) 返回什么?

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

想想 Error 对象上的可枚举属性。

Error 对象具有不可枚举的属性(messagenamestack),因此 JSON.stringify() 返回 {}。这是在 API 响应中发送错误时常见的陷阱。请改用 JSON.stringify(error, Object.getOwnPropertyNames(error)) 或创建一个普通对象。

这两者有什么区别?

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

思考 console.log 处理对象的方式与 JSON 序列化的区别。

console.log(err) 显示错误及其消息和堆栈跟踪,因为控制台对 Error 对象有特殊处理。JSON.stringify(err) 返回 '{}',因为 Error 属性不可枚举。这个差异让许多调试 API 的开发人员措手不及。

这些检查的结果是什么?

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

记住 JavaScript 继承中的原型链。

三者都返回 trueCustomError 继承自 Error,而 Error 继承自 Objectinstanceof 运算符会检查整个原型链,因此 CustomError 的实例同时也是 ErrorObject 的实例。

跨 iframe 使用 instanceof Error 会发生什么?

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

不同的上下文有不同的 Error 构造函数。

instanceof 在不同执行上下文(iframe、worker)中可能返回 false,因为每个上下文都有自己的 Error 构造函数。使用 Object.prototype.toString.call(obj) === '[object Error]' 可在跨上下文时可靠检测错误类型。

当你抛出一个字符串时会发生什么?

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

JavaScript 允许抛出任何值,而不仅仅是 Error 对象。

JavaScript 允许抛出任何值。这里,e instanceof Errorfalsetypeof e"string"。这可能会破坏那些假设所有捕获的异常都是 Error 对象的错误处理代码。为了更好的调试,始终抛出 Error 实例。

err.name 的值是什么?

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

看看 this.constructor.name 的值是什么。

err.name"CustomError",因为 this.constructor.name 返回类名。设置 this.name = this.constructor.name 是一种常见模式,用于确保自定义错误类在堆栈跟踪和错误消息中显示正确的名称。

如果不设置 name,输出是什么?

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

Error 的默认 name 属性是什么?

如果不显式设置 this.name,错误会继承自 Error 类的默认 name 属性,即 "Error"。这就是为什么自定义错误类应该始终在其构造函数中设置 this.name = this.constructor.name

wrapper.cause.message 返回什么?

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

Error.cause 是现代 JavaScript 中用于错误链的特性。

Error.cause(ES2022)允许链式错误以保留原始错误上下文。wrapper.cause 引用原始错误,因此 wrapper.cause.message 返回 "原始错误"。这对于用高层上下文包装底层错误非常有用。

Error.captureStackTrace 的作用是什么?

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

这是 V8 特有的功能,用于生成更清晰的堆栈追踪。

Error.captureStackTrace(V8/Node.js)会从堆栈追踪中移除指定的函数(createError),使得错误工厂函数对最终用户不可见。这样生成的堆栈追踪更清晰,指向的是调用工厂函数的位置,而非工厂函数本身。

错误信息是什么?

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

模板字面量插值如何处理 undefined?

模板字面量在插值过程中会将 undefined 转换为字符串 "undefined"。错误信息变为 "Value undefined is invalid"。为了更清晰的消息,可以考虑在插值前使用 value ?? 'null' 或类似的检查。

什么会被发送到客户端?

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

记住 Error 对象是如何被 JSON.stringify 序列化的。

res.json() 内部使用 JSON.stringify(),因此 Error 对象变成 {}。客户端收到 {"error":{}}。要修复此问题,请使用 res.json({ error: error.message })res.json({ error: { message: error.message, name: error.name } })

Promise.reject() 可以接受什么?

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 拒绝与 throw 语句类似。

throw 类似,Promise.reject() 可以接受任何值——字符串、对象、数字等。这会打印 "string"40442。始终检查 Promise 链中捕获值的类型,尤其是在处理可能以非 Error 值拒绝的第三方代码时。

error.codeerror.errno 的可靠性如何?

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

考虑不同的 JavaScript 环境和错误类型。

codeerrno 这样的属性是特定于环境的(此处指 Node.js),并非标准 Error 对象的一部分。浏览器错误不会包含这些属性。务必检查它们是否存在:if (err.code === 'ENOENT'),而不是假设它们一定存在。

这些检查返回什么?

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

一个检查原型链,另一个检查内部槽位。

instanceof Error 返回 false,因为该对象不是由 Error 构造函数创建的。Object.prototype.toString.call() 也返回 false(它返回 '[object Object]'),因为它检查内部 [[Class]] 槽。这两种方法都正确地将其识别为伪造的错误对象。

掌握错误处理的艺术

从序列化陷阱到跨上下文 instanceof 失败,这些高级概念将初级开发者与经验丰富的饱经摧残的专业人士区分开来。

准备好迎接更多挑战了吗?查看我们的完整测验合集,获取更多关于 JavaScript、算法等的烧脑题目!