详解JavaScript错误捕获:从基础到Promise与性能

作者:小括号时间:2026-10-24 09:46:29浏览:238

本文深入解析JavaScript中的错误捕获机制。详细讲解try-catch-finally的基本用法、同步执行限制及finally对return的影响。介绍Error对象的name、message和stack属性,以及自定义错误对象的规范。重点阐述Promise中错误被内部捕获并沿链传递的机制,包括onreject回调、全局unhandledrejection事件处理。最后探讨V8引擎6.0后try-catch的性能表现,提供实用的错误处理策略。

一、基本使用与逻辑

在JavaScript中,基本的错误捕获结构由try、catch和finally三个关键字组成。try块用于包裹可能抛出异常的代码,catch块用于捕获并处理错误,而finally块中的代码无论是否发生错误都会执行。这种结构为程序提供了基本的容错机制。


try{
    //code....
}catch(err){
    //error handling
}finally{
    //no matter what happens in the try/catch (error or no error), this code in the finally statement should run. 
}

理解错误捕获的逻辑至关重要。try块中的代码执行时,一旦遇到错误,程序流程会立即跳转到catch块。如果没有错误,则跳过catch直接执行finally。即使try或catch中有return语句,finally块仍会执行,这可能改变函数的返回值。

一、基本使用与逻辑

二、特性

try...catch机制仅适用于运行时错误。在解释阶段发生的语法错误无法被try...catch捕获,因为引擎在解析代码时就会报错,导致代码根本无法执行到try块内部。


try{
    {{{{{{{
}catch(err){
    console.error(err)
}
//引擎在‘parse-time'出错,导致无法理解代码,因此无法捕捉

try...catch只能处理同步代码中的错误。对于异步操作,如setTimeout回调函数中发生的错误,由于引擎在回调执行时已经离开了try...catch的作用域,因此无法被捕获。


try{
    setTimeout(function(){
        undefinedVariable;
    },1000)
}catch(err){
    console.error(err)
}
//setTimeout的回调函数执行时,引擎已经离开try...catch结构

finally块具有特殊的优先级,它能让try块中的return语句失效。如果finally块中也包含return语句,那么函数的返回值将由finally块决定,而不是try或catch块。


function test(){
  try {
    return 1;
  } catch(error) {
    return 2;
  } finally {
    return 3;
  }
}
console.log(test());
//3

三、错误对象

当程序发生error时,JavaScript内部会生成一个包含error细节的对象,该对象会被作为参数传进catch块。开发者可以通过这个对象获取错误的详细信息。

对于所有内置错误,错误对象具有两个主要属性:name和message,以及一个非标准的stack属性。

  • name属性表示错误类型,如ReferenceError、SyntaxError等,用于标识错误的种类。
  • message属性是文本类型的错误信息,提供了关于错误原因的详细描述,便于开发者快速定位问题。
  • stack属性是非标准属性,包含发生错误时的调用栈信息,主要用于调试。虽然非标准,但在大多数现代浏览器中都被支持,能帮助开发者追踪错误发生的完整路径。

try {
  lalala; // error, variable is not defined!
} catch (err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)

  // Can also show an error as a whole
  // The error is converted to string as "name: message"
  alert(err); // ReferenceError: lalala is not defined
}

理论上,我们可以throw任何东西作为错误对象,但最好的习惯是throw一个具有name和message属性的对象,以便和内置错误对象保持兼容。这样可以确保错误处理代码的一致性。

番外:内置的错误对象

对象 含义
ReferenceError 引用未定义变量时触发
SyntaxError 使用不合法的语法结构时触发
TypeError 值得类型非预期时触发
URIError 错误使用全局URI函数如encodeURI()、decodeURI()等时触发
RangeError 对Array构造函数使用错误的长度值,对Number.toExponential()、Number.toFixed()或Number.toPrecision()使用无效数字等
EvalError 全局函数eval()中发生的错误

四、较好的catch和throw策略

catch错误不单单是为了防止程序挂掉,更重要的目的是方便调试,找bug。所以对错误的处理策略,稍微可以体现出码者的优雅性。良好的错误处理策略能提升代码的可维护性。

俗话说的好,码者,人恒雅也,尽量遵循一个原则,catch只处理自己知道的错误。对于不确定的错误,应该将其重新抛出,让更上层的代码或全局处理器来处理。

举个梨子(例子)


let json = '{ "age": 30 }'; 
try{
  let user = JSON.parse(json);  
  alert( user.name );
} catch (err) {
  console.error('JSON Error:'+err);
}

上述例子的catch策略能保证程序正常,因为catch块能catch内部所有的错误,无论是JSON.parse出错还是user.name不存在报错,都能被catch到。但两种错误都用同一种打印是不利于调试的,写成下面这样会好一点。通过区分错误类型,可以更精确地处理不同情况。


let json = '{"age":30}'
try{
  let user =  JSON.parse(json);
  alert(user.name)
}catch(err){
   if(err instanceof SyntaxError){
       console.error('JSON Error:'+err);
   }
   else throw err;
}

每个catch块处理自己知道得,可能会出现得错误,就是说,编程人员在编程的时候,catch那些预料到的错误,而将可能自己没料到的错误抛到外面。这样既保证了程序的健壮性,又保留了调试信息。

五、Promise的错误处理

众所周知,Promise是会吞掉error的,因为promise的实现就在内部对所有error进行了捕获,且捕获到的error不是向外抛出(外指promise之外),而是沿着链找到最近的onreject回调传入,所以promise的错误处理只有两种办法:设置onreject回调或全局捕获。

  1. 设置onreject回调是处理Promise错误的直接方式。在Promise链中,通过.catch()或.then(null, onreject)可以指定错误处理函数。
  2. 全局捕获是处理未处理Promise错误的备用方案。当Promise链中没有设置onreject回调时,错误会冒泡到全局环境。

举个栗子(例子)


try{
    new Promise((resolve,reject)=>{
        throw new Error('promise error')
    }).catch(()=>{
        //错误在最近的onreject回调被捕获
        console.error(err);
    })
}catch(err){
    //永远不会执行,promise吞掉error
    console.error(err);
}

另外需要注意,无论是执行者函数(executor)和还是 promise 的处理程序(handler),内部发生的错误统统吞掉,相当于被隐式catch,error会自动找到最近的onreject回调传进去。这意味着在Promise内部抛出的错误不会向外传播到try-catch块。


try{
    new Promise((resolve,reject)=>{
        resolve();
    }).then(()=>{
        throw new Error('promise then error');
    }).catch((err){
        console.error(err);
    })
}catch(err){
    //地球毁灭之前都不会执行
    console.error(err)
}

同理,在错误找到onreject传进去之前,经过的then注册的onfulfilled回调统统失效,直到找到onreject回调,处理之后,onreject回调之后的onfulfilled回调才正常。这体现了Promise链中错误处理的传递特性。


try {
    new Promise((resolve, reject) => {
        throw new Error('promise error')
    }).then((ret) => {
        //错误没有处理,失效
        console.log('then1:' + ret)
    }).catch((err) => {
        //错误处理了,后序正常
        console.error(err);
        return 'handled'
    }).then((ret) => {
        //正常执行
        console.log('then2' + ret);
    })
} catch (err) {
    //同样的,人类毁灭之前都不会执行
    console.error(err)
}

// Error:promise error
//then2handled

那整条链一个catch都没设置会怎么样呢?

那这个error就会击穿地心,一直穿透到全局,根据宿主环境的不同触发不同的全局事件,比如说浏览器中会触发 unhandledrejection事件,node环境中也会触发unhandledRejection事件,一般会对这事件进行监听,再显示信息给编程人员或者用户。这是防止Promise错误被静默忽略的最后防线。

番外1:chromium / v8 / v8 / 3.29.45 的 Promise内部错误捕捉

一、基本使用与逻辑

番外2:async/await错误捕捉

六、性能损耗

After V8 version 6 (shipped with Node 8.3 and latest Chrome), the performance of code inside try-catch is the same as that of normal code. ------ 爆栈网

(稍微测了一下,相差无几)

以上就是详解JavaScript错误捕获的详细内容,更多关于JavaScript 错误捕获的资料请关注本站其它相关文章!

作者最新文章