应仅在流程不可继续时用 throw,如参数为 null、配置缺失或 token 过期;业务校验错误应 return 结构化对象。Java 多用 IllegalArgumentException,Promise 中须用 .catch() 或 try/catch 捕获,异步回调中裸 throw 会导致 uncaughtException。

直接抛 new Error() 是最常见也最容易出错的做法——它能中断流程,但往往让调用方无从判断错误类型,也掩盖了真正该返回结构化错误的场景。
什么时候该用 throw,而不是 return { error: 'xxx' }
throw 的核心作用是「流程不可继续」,不是「告诉用户错了」。比如:
- 参数为
null或undefined,而后续所有逻辑都依赖这个值(如generateOrder(null)) - 配置项缺失导致整个模块初始化失败(如
DB_URL未设置却调用了connectDB()) - 调用第三方 SDK 前发现 token 已过期且无法刷新,再往下走必然 401
反例:用户名长度校验、邮箱格式不合法、密码强度不足——这些属于「可预期的业务输入问题」,应 return { success: false, error: 'invalid_email' },而非抛异常。否则上层必须 try/catch 每次调用,反而模糊了真正需要关注的系统级异常。
Java 中 throw new IllegalArgumentException 的典型写法
Java 里最常用于参数校验的是 IllegalArgumentException,它继承自 RuntimeException,无需在方法签名加 throws,调用方也不强制处理,语义清晰又轻量:
public void processOrder(Order order) {
if (order == null) {
throw new IllegalArgumentException("order cannot be null");
}
if (order.getAmount()
<p>注意两点:</p>
- 不要用
new Exception("...")—— 它是受检异常,强制调用方处理,但参数错误不该强迫业务代码写try/catch - 避免只抛字符串:
throw "order is null"在 Java 中根本编译不过;JS 虽支持,但丢堆栈,绝对不用
自定义业务异常类该怎么设计
当「库存不足」「支付超时」「风控拒绝」这类错误需要差异化处理时,才值得建自定义异常。关键原则:
- 继承
RuntimeException(非受检),除非你明确要求调用方必须try/catch或声明throws - 固定加
name字段(JS)或public final String code(Java),方便上层用err.name === 'InventoryError'或err.getCode().equals("INSUFFICIENT_STOCK")判断 - 构造函数里传入原始错误上下文,比如被拒绝的订单 ID、扣减的 SKU 编码,别只留一句「操作失败」
示例(Java):
public class InventoryException extends RuntimeException {
private final String skuCode;
public InventoryException(String skuCode, String message) {
super(message);
this.skuCode = skuCode;
}
public String getSkuCode() { return skuCode; }
}
使用时:
CompletableFuture
- Promise 内部用
.catch()或await配合try/catch - Java 的
CompletableFuture用handle()或exceptionally()捕获 - 千万别在
setTimeout、requestIdleCallback这类纯异步回调里裸 throw——它会变成uncaughtException,进程可能退出
最隐蔽的问题是:你以为自己在同步校验,其实参数来自网络请求或数据库查询,实际执行时已是异步上下文。这时候 throw 的位置和捕获点必须严格对齐。










