Index

preserve-caught-error

重新抛出自定义错误时,禁止丢失最初捕获的错误

✅ Recommended

在 配置文件 中使用来自 @eslint/js 的 recommended 配置可以启用此规则

💡 hasSuggestions

此规则报告的一些问题可通过编辑器 建议 手动修复

JavaScript 开发者经常在 catch 块中重新抛出错误以添加上下文,但忘记保留原始错误,导致调试信息丢失。

🌐 JavaScript developers often re-throw errors in catch blocks to add context but forget to preserve the original error, resulting in lost debugging information.

在抛出新错误时使用 cause 选项有助于保留原始错误并保持完整的错误链,从而提高可调试性和可追踪性。

🌐 Using the cause option when throwing new errors helps retain the original error and maintain complete error chains, which improves debuggability and traceability.

try {
	await fetch("https://xyz.com/resource");
} catch(error) {
	// Throw a more specific error without losing original context
	throw new Error("Failed to fetch resource", {
		cause: error
	});
}

规则详情

🌐 Rule Details

此规则强制在 catch 块中抛出新错误时使用 cause 属性。

🌐 This rule enforces the use of the cause property when throwing a new error inside a catch block.

检查所有支持传递 cause 的内置 error types。

🌐 Checks for all built-in error types that support passing a cause.

此规则的错误代码示例:

🌐 Examples of incorrect code for this rule:

在线运行
/* eslint preserve-caught-error: "error" */

// Not using the `cause` option
try {
    // ...
} catch (error) {
    throw new Error("Something went wrong: " + error.message);
}

// Throwing a new Error with unrelated cause
try {
	doSomething();
} catch (err) {
	const unrelated = new Error("other");
	throw new Error("Something failed", { cause: unrelated });
}

// Caught error is being lost partially due to destructuring
try {
	doSomething();
} catch ({ message, ...rest }) {
	throw new Error(message);
}

// Cause error is being shadowed by a closer scoped redeclaration.
try {
    doSomething();
} catch (error) {
    if (whatever) {
        const error = anotherError; // This declaration is the problem.
        throw new Error("Something went wrong", { cause: error });
    }
}

符合此规则的正确代码示例:

🌐 Examples of correct code for this rule:

在线运行
/* eslint preserve-caught-error: "error" */

try {
    // ...
} catch (error) {
    throw new Error("Something went wrong", { cause: error });
}

// When the thrown error is not directly related to the caught error.
try {
} catch (error) {
	foo = {
		bar() {
			// This throw is not directly related to the caught error.
			throw new Error("Something went wrong");
		}
	};
}

// No throw inside catch
try {
    doSomething();
} catch (e) {
    console.error(e);
}

// Ignoring the caught error at the parameter level
// This is valid by default, but this behavior can be changed
// by using the `requireCatchParameter` option discussed below.
try {
	doSomething();
} catch {
	throw new TypeError("Something went wrong");
}

选项

🌐 Options

此规则接受一个选项——一个具有以下可选属性的对象:

🌐 This rule takes a single option — an object with the following optional properties:

  • requireCatchParameter:当设置为 true 时,要求 catch 块始终包含被捕获的错误参数。默认情况下为 false。
  • errorClassNames:要检查的其他错误类名称以保留原因。默认情况下,这是 []。

requireCatchParameter

启用此选项要求所有的 catch 块都必须有一个捕获的错误参数。这确保了捕获的错误不会在参数级别被丢弃。

🌐 Enabling this option mandates for all the catch blocks to have a caught error parameter. This makes sure that the caught error is not discarded at the parameter level.

"preserve-caught-error": ["error", {
  "requireCatchParameter": true
}]

针对 { "requireCatchParameter": true } 选项的错误代码示例:

🌐 Example of incorrect code for the { "requireCatchParameter": true } option:

在线运行
/* eslint preserve-caught-error: ["error", { "requireCatchParameter": true }] */

try {
	doSomething();
} catch { // Can't discard the error ❌
	throw new Error("Something went wrong");
}

{ "requireCatchParameter": true } 选项的正确代码示例:

🌐 Example of correct code for the { "requireCatchParameter": true } option:

在线运行
/* eslint preserve-caught-error: ["error", { "requireCatchParameter": true }] */

try {
	doSomething();
} catch(error) { // Error is being referenced ✅
	// Handling and re-throw logic
}

errorClassNames

默认情况下,此规则仅检查内置的 Error 类型(Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError、AggregateError)。使用 errorClassNames 也可以检查自定义错误类。

🌐 By default, this rule checks only the built-in Error types (Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, AggregateError). Use errorClassNames to also check custom error classes.

每个条目可以是字符串或对象:

🌐 Each entry can be either a string or an object:

  • 字符串指定类名。构造函数假定接受选项对象作为第二个参数,与内置的Error签名匹配。
  • 当构造函数在不同位置接受选项对象时,会使用带有 name 和 argumentPosition 的 对象。argumentPosition 是从1开始计数的。
{
    "rules": {
        "preserve-caught-error": ["error", {
            "errorClassNames": [
                "AppError",
                { "name": "APIError", "argumentPosition": 3 }
            ]
        }]
    }
}

针对 { "errorClassNames": ["AppError"] } 选项的错误代码示例:

🌐 Example of incorrect code for the { "errorClassNames": ["AppError"] } option:

在线运行
/* eslint preserve-caught-error: ["error", { "errorClassNames": ["AppError"] }] */

class AppError extends Error {}

try {
	doSomething();
} catch (err) {
	throw new AppError("Something failed");
}

{ "errorClassNames": ["AppError"] } 选项的正确代码示例:

🌐 Example of correct code for the { "errorClassNames": ["AppError"] } option:

在线运行
/* eslint preserve-caught-error: ["error", { "errorClassNames": ["AppError"] }] */

class AppError extends Error {}

try {
	doSomething();
} catch (err) {
	throw new AppError("Something failed", { cause: err });
}

针对 { "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] } 选项的错误代码示例:

🌐 Example of incorrect code for the { "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] } option:

在线运行
/* eslint preserve-caught-error: ["error", { "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] }] */

class APIError extends Error {
	constructor(message, statusCode, options) {
		super(message, options);
		this.statusCode = statusCode;
	}
}

try {
	doSomething();
} catch (err) {
	throw new APIError("Request failed", 500);
}

{ "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] } 选项的正确代码示例:

🌐 Example of correct code for the { "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] } option:

在线运行
/* eslint preserve-caught-error: ["error", { "errorClassNames": [{ "name": "APIError", "argumentPosition": 3 }] }] */

class APIError extends Error {
	constructor(message, statusCode, options) {
		super(message, options);
		this.statusCode = statusCode;
	}
}

try {
	doSomething();
} catch (err) {
	throw new APIError("Request failed", 500, { cause: err });
}

已知限制

🌐 Known Limitations

errorClassNames 选项接受的是名字,而不是特定错误类的引用。这个规则会在 AST 中匹配这些配置的名字,但不会解析作用域或类型信息。

🌐 The errorClassNames option accepts names, not references to specific error classes. The rule matches these configured names in the AST and does not resolve scope or type information.

因此,本地类可能会遮蔽预期的错误类,即使它们名字相同。这个规则无法区分本地类和预期类,可能会报出错误的警告。

🌐 As a result, a local class can shadow the intended error class while having the same name. The rule cannot distinguish the local class from the intended class and may report a false positive.

例如:

🌐 For example:

/* eslint preserve-caught-error: ["error", { errorClassNames: ["AppError"] }] */

function makeWrapped() {
	class AppError {
		constructor(err) {
			this.original = err;
		}
	}

	try {
		doSomething();
	} catch (err) {
		throw new AppError(err);
	}
}

这里,AppError 是按名字配置的,但本地的 AppError 类和预期的全局/导入类不同。这个规则仍然会匹配名字,并可能报告缺少 cause,即使这个本地类有不同的构造函数签名,并且不接受包含 cause 的选项对象。

🌐 Here, AppError is configured by name, but the local AppError class is different from the intended global/imported class. The rule still matches the name and may report a missing cause, even though this local class has a different constructor signature and does not accept an options object containing cause.

何时不使用

🌐 When Not To Use It

如果你符合以下情况,则可能不想启用此规则:

🌐 You might not want to enable this rule if:

  • 你遵循自定义错误处理方法,其中故意从重新抛出的错误中省略原始错误(例如,为了避免暴露内部详细信息或单独记录原始错误)。
  • 你使用第三方或内部的错误处理库,这些库通过非标准属性(例如 verror)保留错误上下文,而不是使用 cause 选项。
  • (在罕见情况下)你正在针对不支持 Error 构造函数中 cause 选项的旧环境。

版本

此规则是在 ESLint v9.35.0 中引入。

进阶读物

资源