引言
没有人能写出永远不出错的代码。真正区分水平的,不是谁不出错,而是出错之后怎么办。错误处理做得好,程序能优雅降级、给出有用提示、快速定位问题;做得差,用户看到白屏,开发者对着日志发呆。这篇文章不带代码,把 JS 的错误处理讲清楚。
一、错误是什么
错误,是程序在执行过程中遇到的非正常情况。它可能来自代码本身写错了,比如访问了不存在的属性;也可能来自外部环境,比如网络断了、用户输入了非法内容。
错误分两类。一类是语法错误,代码还没运行就被检查出来了,根本执行不了。另一类是运行时错误,代码能跑起来,但跑到某一行出了问题。
还有一类不算严格意义的错误,叫异常,通常指可以预料、可以处理的问题。比如请求超时、文件不存在。这类问题不该让程序崩溃,而应该被捕获、被处理。
二、不处理会怎样
默认情况下,一个未处理的错误会让当前执行流程直接中断。
在浏览器里,如果错误发生在页面渲染过程中,可能导致部分内容显示不出来。如果发生在事件处理里,这个交互就失效了。更糟的是,有些错误会中断后续所有逻辑,用户看到的是一个卡死的页面。
在服务器端,未处理的错误可能导致整个服务进程退出,影响所有用户。
所以,错误处理不是锦上添花,而是保障程序稳定运行的基本要求。
三、捕获错误的基本思路
JS 提供了一套捕获机制,核心思想是:把可能出错的代码包起来,出了问题就跳到专门的善后逻辑,而不是让程序崩溃。
这套机制分几个部分。尝试执行的代码放在一个块里;一旦出错,控制权立刻转移到处理块;处理块里可以做补救、记录日志、给用户提示。无论是否出错,最后还可以有一个收尾块,用来做清理工作。
这种结构的好处是,正常的逻辑和出错的逻辑分开写,代码更清晰,也不会因为一个错误就让整个函数失效。
四、错误对象里有什么
捕获到的错误,通常是一个对象,里面包含一些有用信息。
最重要的是错误信息,描述了出了什么问题。还有错误类型,说明是哪一类错误。以及调用栈,记录了错误发生时函数是怎么一层层调用的。
调用栈尤其有价值。它像一张路线图,告诉你错误从哪来、经过了哪些函数。排查问题时,顺着调用栈往下看,往往能迅速定位到根源。
五、主动抛出错误
除了被动等待错误发生,我们还可以主动抛出错误。
什么时候需要主动抛?当检测到不符合预期的输入或状态时。比如一个函数要求参数必须是正数,传进来负数,与其让它默默算出一个错误结果,不如直接抛出错误,让调用者知道出了问题。
主动抛错的好处是尽早暴露问题。错误越早被发现,修复成本越低。如果让它悄悄传递下去,可能到很远的地方才爆发,那时候排查就难了。
抛错时,可以自定义错误信息,也可以使用不同类型的错误,方便上层分类处理。
六、异步代码中的错误
异步代码的错误处理,比同步代码麻烦。
因为异步任务的执行时机不确定,它出错时可能已经不在原来的调用栈里了。传统的捕获机制只能包住同步部分,管不到异步回调里发生的错误。
针对这个问题,不同阶段有不同的解决方案。早期的回调模式,约定用回调的第一个参数传递错误,调用者必须每次都检查。后来的 Promise,提供了专门的失败处理通道,可以链式地在末尾统一捕获。再后来的异步函数写法,可以直接用同步的捕获机制来处理异步错误,心智负担大大降低。
理解这条演进线,你就知道为什么现代 JS 推荐用异步函数写法,因为它让错误处理回归简单。
七、错误处理的常见误区
第一个误区:捕获了却什么都不做。这样错误被吞掉了,问题依然存在,只是更难发现。捕获之后至少应该记录日志。
第二个误区:捕获范围过大。把一大段代码全包进去,出了问题根本不知道是哪一行。应该尽量缩小捕获范围,只包真正可能出错的部分。
第三个误区:用错误处理代替正常逻辑。有些情况是正常分支,不是错误。比如用户没填某个可选字段,这是正常的,不该当成错误处理。滥用错误处理会让代码难以理解。
第四个误区:不区分错误类型。所有错误一视同仁地处理,可能掩盖了真正需要关注的问题。应该根据错误类型做不同应对。
八、总结
错误处理的目标不是消灭错误,而是让程序在出错时依然可控。理解错误的来源和分类,掌握捕获、抛出、异步处理的机制,避开常见误区,你写的程序才能从"能跑"升级到"可靠"。这是工程能力的重要体现,也是从写 Demo 到做产品的必经之路。