错误处理,“try...catch“

本文介绍了JavaScript中的try...catch语句,如何捕获并处理运行时错误,包括错误对象的结构、使用throw抛出自定义错误、再次抛出的技巧以及全局错误处理机制。最后提到finally子句的应用和Error对象的属性。
摘要由CSDN通过智能技术生成

通常,如果发生错误,脚本就会“死亡”(立即停止),并在控制台将错误打印出来。但是有一种语法结构 try...catch,它使我们可以“捕获(catch)”错误,因此脚本可以执行更合理的操作,而不是死掉。

try...catch语法:

try {

  // 代码...

} catch (err) {

  // 错误捕获

}

try...catch按照以下步骤执行:

首先,执行 try {...} 中的代码。

如果这里没有错误,则忽略 catch (err):执行到 try 的末尾并跳过 catch 继续执行。

如果这里出现错误,则 try 执行停止,控制流转向 catch (err) 的开头。变量 err(我们可以使用任何名称)将包含一个 error 对象,该对象包含了所发生事件的详细信息。所以,try {...} 块内的错误不会杀死脚本 — 我们有机会在 catch 中处理它。

example

try {
  alert('开始执行 try 中的内容');  // (1) <--
  // ...这里没有 error
  alert('try 中的内容执行完毕');   // (2) <--
} catch (err) {
  alert('catch 被忽略,因为没有 error'); // (3)
}

//catch
   try{
        console.log('a', a)
     }catch (e) {
      console.log('11', e)

      }

小提示

try...catch 仅对运行时的 error 有效

要使得 try...catch 能工作,代码必须是可执行的,必须是有效的 JavaScript 代码,无语法错误。

所以,try...catch 只能处理有效代码中出现的错误。这类错误被称为“运行时的错误(runtime errors)”,有时被称为“异常(exceptions)

   try{
        console.log('a', a)
     }catch (e) {
      console.log('11', e)

      }

try...catch 同步工作

如果在“计划的(scheduled)”代码中发生异常,例如在 setTimeout 中,则 try...catch 不会捕获到异常

    try {
        alert('success')
        setTimeout(function () {
            noSuchVariable; // 脚本将在这里停止运行
        }, 1000);
    } catch (err) {
        alert("won't work");
    }

因为 try...catch 包裹了计划要执行的函数,该函数本身要稍后才执行,这时引擎已经离开了 try...catch 结构。

为了捕获到计划的(scheduled)函数中的异常,那么 try...catch 必须在这个函数内:

setTimeout(function() {
  try {
    noSuchVariable; // try...catch 处理 error 了!
  } catch {
    alert( "error is caught here!" );
  }
}, 1000);

Error 对象

发生错误时,JavaScript 生成一个包含有关其详细信息的对象。然后将该对象作为参数传递给 catch:

try {
  // ...
} catch (err) { // <-- “error 对象”,也可以用其他参数名代替 err
  // ...
}
 try {
        alert("aaaa")
        alert(aa);
    } catch (err) { // <-- “error 对象”,也可以用其他参数名代替 err
        // ...
        alert(err);
    }

对于所有内建的 error,error 对象具有两个主要属性:

name

Error 名称。例如,对于一个未定义的变量,名称是 "ReferenceError"。

message

关于 error 的详细文字描述。

还有其他非标准的属性在大多数环境中可用。其中被最广泛使用和支持的是:

stack

当前的调用栈:用于调试目的的一个字符串,其中包含有关导致 error 的嵌套调用序列的信息。

    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)
        // 也可以将一个 error 作为整体显示出来as a whole
        // Error 信息被转换为像 "name: message" 这样的字符串
        alert(err); // ReferenceError: lalala is not defined
    }

可选的 “catch” 绑定

如果我们不需要 error 的详细信息,catch 也可以忽略它:

try {
  // ...
} catch { // <-- 没有 (err)
  // ...
}

当报错只显示信息无法满足我们的需求时,可以做更多的事儿:发送一个新的网络请求,向访问者建议一个替代方案,将有关错误的信息发送给记录日志的设备,……

throw抛出我们自定义的 error

如果这个 json 在语法上是正确的,但是没有所必须的 name 属性该怎么办

let json = '{ "age": 30 }'; // 不完整的数据
try {
  let user = JSON.parse(json); // <-- 没有 error
  alert( user.name ); // 没有 name!

} catch (err) {
  alert( "doesn't execute" );
}

这里 JSON.parse 正常执行,但是缺少 name 属性对我们来说确实是个 error。为了统一进行 error 处理,我们将使用 throw 操作符。

throw 操作符

throw 操作符会生成一个 error 对象。

throw <error object>

技术上讲,我们可以将任何东西用作 error 对象。甚至可以是一个原始类型数据,例如数字或字符串,但最好使用对象,最好使用具有 name 和 message 属性的对象(某种程度上保持与内建 error 的兼容性)。

JavaScript 中有很多内建的标准 error 的构造器:Error,SyntaxError,ReferenceError,TypeError 等。我们也可以使用它们来创建 error 对象。

   let message = "?"
    let error = new Error(message)
    let error1 = new SyntaxError(message);
    let error2 = new ReferenceError(message);
    console.log('error1', error1, error2)

对于内建的 error(不是对于其他任何对象,仅仅是对于 error),name 属性刚好就是构造器的名字。message 则来自于参数(argument)。

let error = new Error("Things happen o_O");
alert(error.name); // Error
alert(error.message); // Things happen o_O
JSON.parse 会生成一个SyntaxError错误
try {
  JSON.parse("{ bad json o_O }");
} catch(err) {
  alert(err.name); // SyntaxError
  alert(err.message); // Unexpected token b in JSON at position 2
}

缺少 name 属性就是一个 error,因为用户必须有一个 name。所以,

抛出这个 error
  let json = '{ "age": 30 }'; // 不完整的数据
    try {
        let user = JSON.parse(json); // <-- 没有 error
        if (!user.name) {
            throw new SyntaxError('Incomplete data: no name') // (*)
        }
        alert(user.name);
    } catch (err) {
        alert("JSON Error: " + err.message); // JSON Error: Incomplete data: no name
    }

在 (*) 标记的这一行,throw 操作符生成了包含着我们所给定的 message 的 SyntaxError,与 JavaScript 自己生成的方式相同。try 的执行立即停止,控制流转向 catch 块。

现在,catch 成为了所有 error 处理的唯一场所:对于 JSON.parse 和其他情况都适用。

再次抛出(Rethrowing)

try...catch 旨在捕获“数据不正确”的 error。但是实际上,catch 会捕获到 所有 来自于 try 的 error。在这儿,它捕获到了一个预料之外的 error,但是仍然抛出的是同样的 "Error" 信息。这是不正确的,并且也会使代码变得更难以调试。

为了避免此类问题,我们可以采用“重新抛出”技术。规则很简单:catch 应该只处理它知道的 error,并“抛出”所有其他 error。

  • Catch 捕获所有 error。
  • 在 catch (err) {...} 块中,我们对 error 对象 err 进行分析。
  • 如果我们不知道如何处理它,那我们就 throw err。

通常,我们可以使用 instanceof 操作符判断错误类型,我们还可以从 err.name 属性中获取错误的类名。所有原生的错误都有这个属性。另一种方式是读取 err.constructor.name。

使用“再次抛出”,以达到在 catch 中只处理 SyntaxError 的目的:

 let json = '{ "age": 30 }'; // 不完整的数据
    try {
        let user = JSON.parse(json); // <-- 没有 error
        if (!user.name) {
            throw new SyntaxError('Incomplete data: no name') // (*)
        }
        getSd();
        alert(user.name);
    } catch (err) {
        if (err instanceof SyntaxError) {
            alert('Json Error' + err.message);
        } else {
            throw err;
        }
    }

如果 (*) 标记的这行 catch 块中的 error 从 try...catch 中“掉了出来”,那么它也可以被外部的 try...catch 结构(如果存在)捕获到,如果外部不存在这种结构,那么脚本就会被杀死。

所以,catch 块实际上只处理它知道该如何处理的 error,并“跳过”所有其他的 error。

function readData() {
  let json = '{ "age": 30 }';
  try {
    // ...
    blabla(); // error!
  } catch (err) {
    // ...
    if (!(err instanceof SyntaxError)) {
      throw err; // 再次抛出(不知道如何处理它)
    }
  }
}
try {
  readData();
} catch (err) {
  alert( "External catch got: " + err ); // 捕获了它!
}

readData 只知道如何处理 SyntaxError,而外部的 try...catch 知道如何处理任意的 error。

try...catch...finaly

try...catch 结构可能还有一个代码子句(clause):finally。

如果它存在,它在所有情况下都会被执行:

  • try 之后,如果没有 error,
  • catch 之后,如果没有 error。
try {
   ... 尝试执行的代码 ...
} catch (err) {
   ... 处理 error ...
} finally {
   ... 总是会执行的代码 ...
}

example

    try {
        alert('try');
        if (confirm('Make an error?')) BAD_CODE();
    } catch (err) {
        alert('catch');
    } finally {
        alert('finally');
    }

这段代码有两种执行方式:

  • 如果你对于 “Make an error?” 的回答是 “Yes”,那么执行 try -> catch -> finally。
  • 如果你的回答是 “No”,那么执行 try -> finally。

finally 子句(clause)通常用在:当我们开始做某事的时候,希望无论出现什么情况都要完成完成某个任务。

无论如何,finally 子句都是一个结束测量的好地方

在这儿,finally 能够保证在两种情况下都能正确地测量时间 — 成功执行 fib 以及 fib 中出现 error 时

换句话说,函数 fib 以 return 还是 throw 完成都无关紧要。在这两种情况下都会执行 finally 子句。

finally 和 return

finally 子句适用于 try...catch 的 任何 出口。这包括显式的 return

    function func() {
        try {
            return 1;
        } catch (err) {
            /* ... */
        } finally {
            alert('finally');
        }
    }
    alert(func()); // 先执行 finally 中的 alert,然后执行这个 alert

try...finally

没有 catch 子句的 try...finally 结构也很有用。当我们不想在这儿处理 error(让它们 fall through),

function func() {
  // 开始执行需要被完成的操作(比如测量)
  try {
    // ...
  } finally {
    // 完成前面我们需要完成的那件事儿,即使 try 中的执行失败了
  }
}

上面的代码中,由于没有 catch,所以 try 中的 error 总是会使代码执行跳转至函数 func() 外。但是,在跳出之前需要执行 finally 中的代码。

全局 catch

设想一下,在 try...catch 结构外有一个致命的 error,然后脚本死亡了。这个 error 就像编程错误或其他可怕的事儿那样,浏览器中,我们可以将一个函数赋值给特殊的 window.onerror 属性,该函数将在发生未捕获的 error 时执行。

window.onerror = function(message, url, line, col, error) {
  // ...
};
  • message
    • Error 信息。
  • url
    • 发生 error 的脚本的 URL。
  • line,col
    • 发生 error 处的代码的行号和列号。
  • error
    • Error 对象。
  window.onerror = function (message, url, line, col, error) {
        alert(`${message}\n At ${line}:${col} of ${url}`);
    };
    function readData() {
        badFunc(); // 啊,出问题了!
    }
    readData();

全局错误处理程序 window.onerror 的作用通常不是恢复脚本的执行 — 如果发生编程错误,那这几乎是不可能的,它的作用是将错误信息发送给开发者。

总结

try...catch 结构允许我们处理执行过程中出现的 error。从字面上看,它允许“尝试”运行代码并“捕获”其中可能发生的错误。

try {
  // 执行此处代码
} catch (err) {
  // 如果发生错误,跳转至此处
  // err 是一个 error 对象
} finally {
  // 无论怎样都会在 try/catch 之后执行
}

实际使用可能会没有 catch 部分或者没有 finally,所以 try...catch 或 try...finally 都是可用的。

Error 对象包含下列属性:
  • message — 人类可读的 error 信息。
  • name — 具有 error 名称的字符串(Error 构造器的名称)。
  • stack(没有标准,但得到了很好的支持)— Error 发生时的调用栈。

如果我们不需要 error 对象,我们可以通过使用 catch { 而不是 catch (err) { 来省略它。

我们也可以使用 throw 操作符来生成自定义的 error。从技术上讲,throw 的参数可以是任何东西,但通常是继承自内建的 Error 类的 error 对象。下一章我们会详细介绍扩展 error。

再次抛出(rethrowing)是一种错误处理的重要模式:catch 块通常期望并知道如何处理特定的 error 类型,因此它应该再次抛出它不知道的 error。

即使我们没有 try...catch,大多数执行环境也允许我们设置“全局”错误处理程序来捕获“掉出(fall out)”的 error。在浏览器中,就是 window.onerror。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
try...catch是一种异常处理机制,在代码中可以用来捕获并处理可能发生的异常。它的基本格式是: try { // 可能会产生异常的代码 } catch (ExceptionType exceptionVariable) { // 异常处理代码 } 在try块中放置可能会抛出异常的代码,如果这部分代码发生异常,程序会立即跳转到与异常类型相匹配的catch块中执行异常处理代码。catch块中的代码用于处理异常,并采取相应的措施,比如打印错误信息、修复错误或者进行其他必要的操作。 try...catch的好处是,它可以保证即使有异常发生,程序也不会中断执行,而是会继续执行其他的代码。这对于确保程序的稳定性和可靠性非常重要。 在使用多个catch块处理异常时,需要注意一些规则。首先,多个catch块中的异常类型不能相同,否则会导致编译错误。其次,如果多个catch块中的异常类型存在继承关系,子类异常应该在上面的catch块中处理,父类异常应该在下面的catch块中处理。最后,当发生异常时,多个catch块只会执行一个,按照从上到下的顺序进行匹配。 与try...catch不同,throw是将异常抛给调用它的上一级代码。如果在代码中抛出异常,后续的代码将不会执行。throw通常用于在异常无法直接处理的情况下,将异常传递给调用者或其他地方进行处理。 综上所述,try...catch是一种用于捕获和处理异常的机制,可以保证程序在发生异常时不会中断执行,并提供了灵活的异常处理方式。throw则是将异常抛给上一级代码,以便进行进一步处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值