c++ 空指针 捕获 try catch语句_② - throw、try、catch、finally - JS异常处理

个人学习笔记,对基础知识的整理和巩固。

JavaScript提供了捕获和处理异常的功能。

类似以下操作均会触发错误:

// ①
console.log(不存在的变量);
// ②
console.log(window.不存在的属性.不存在的属性);

当发生错误时,代码将停止运行,同时抛出一个错误对象,它是Error类的一个实例。

我们会在控制台看到它:

e44e02e8067dfdae333875cdf27850f4.png

但更多情况下,我们会看到更具体的错误,它们总共有六类,包括EvalError、InternalError等等(更多:Error类型),它们都是Error的子类,分别表示不同的错误原因。

除此之外,我们自己也可以主动触发错误:

▉ throw[1]

首先,我们需要创建一个错误对象,只需要把错误信息传递给Error(或者具体的EvalError等)就可以了:

let error = new Error("发生了一个错误");

现在我们得到了一个错误对象,接下来只要使用throw语句把它抛出:

throw error;

控制台立刻输出了这个错误:

3b3cc9e609cfb3a0fea4b1c9b2a4948c.png

这样,我们就主动触发了一个自己创建的错误,执行到这里的时候,代码的运行将中断。

事实上,throw可以抛出的内容不仅仅是Error对象,数字、字符串甚至是对象、函数等等都可以。

throw true?21:20; // 表达式 抛出数字21
throw "出错了"; // 字符串
throw function(){console.log("出错了")}; // 函数
throw {status:"error",msg:"出错了"}; // 对象

控制台都会如实输出:

7bdd8c2a86a844f0416ece7d79480453.png

不过大多数时候,只是中断执行并且抛出和输出错误并不是我们的目的,或者甚至说是我们想要避免的。

我们希望代码即使出现错误的时候也能继续运行而不至于影响大局,我们希望能够对可预见的错误做出相应的处理。

所以我们需要异常(错误)处理:

▉ try、catch[2]

try和catch就是用来处理错误的工具,在“try块”里我们运行可能会出错的代码,然后当出错的时候用catch来捕获并作出相应处理。

try{
  // 一段可能会出错的代码 出错时抛出一个错误
}catch(e){
  // 捕获
  console.log(e);
}
// 后面的代码将接着执行

这里的错误可能是执行出错(被动抛出),也可能是我们用throw主动抛出错误,前者会抛出一个Error(也可能是子类如EvalError)的实例对象,后者可能是throw出的任何值。

任何错误的抛出,try块里的代码都会立刻中断,同时将它传递到catch块,并接着运行catch块代码,之后,如果catch块内没有发生错误的话,将继续往下执行。

catch后面的括号用来接收捕获到的错误,可用作判断处理。

被捕获(catch)的错误不会输出到控制台(我们很容易可以注意到前面控制台输出的错误都以“Uncaught”开头)

如果try块内没有抛出错误,catch块将不会执行。

▉ 异常处理的嵌套

既然try块内可以像其他地方一样执行代码,那么当然也可以在try块(或者catch块)里再写一个try和catch的组合:

try {
  try {
    throw 1;
  } catch (e) {
    throw e; // 捕获了错误 但又重新抛出
  }
} catch (e) {
  console.log(e); // 输出1
}

在这个示例里,次级try块内抛出1,被其相应的catch捕获,然而这个catch块并未对这个错误做任何处理,而是把它又一次抛了出来,因为它运行在上级的try块里,所以这个错误又再一次被上级try对应的catch捕获,从而输出1。

所以我们知道:

  • try或catch里可以嵌套try...catch
  • catch块内仍然可能发生错误
  • 错误会逐级向上抛出,直到被捕捉,到最顶层而未被捕捉的错误将被输出到控制台
  • 错误总是被包裹它的最内层的try所对应的catch捕获

▉ finally

除了catch之外,还可以加上finally语句块。

try{
  // 一段可能会出错的代码 出错时抛出一个错误
}catch(e){
  // 捕获
  console.log(e);
}finally{
  // 一些善后代码
}

不管try块内有没有抛出错误,finally语句块总会被执行。

如果try块内发生错误,finally块将在catch块之后被执行;如果没有发生错误,将跳过catch块,直接运行finally块。

我们其实还可以不写catch块(try块后必须至少跟一个catch或finally,不能只写try)

try {
    try {
        throw 1;
    } finally {
        // 一些善后代码
        console.log("finally"); // 输出:finally
    }
    // 不会向下执行
    console.log("不会输出");
} catch (e) {
    console.log(e); // 输出1
}

假如不写catch块,try块内抛出错误之后,会先执行finally块,之后,因为错误并未被捕捉,所以将继续向外层抛出(抛出错误即意味着原位置运行中断)

如果finally块内发生错误会怎样呢?

■ 错误的覆盖

不管try块和catch块内有没有抛出错误,如果finally块内抛出错误,外层代码只能接收到finally块内抛出的错误。

try {
    try {
        throw 1; // 位置①
    } catch (err) {
        throw 2; // 位置②
    } finally {
        throw 3; // 位置③
    }
} catch (err) {
    console.log(err); // 输出3
}

在上面这个例子里,内层try块内(位置①)抛出的错误1被捕获,接着其catch块内(位置②)又抛出了一个错误2,之后将立刻运行finally块,finally块内(位置③)竟然又抛出了一个错误3,这个错误替代了catch块内抛出的错误,所以这一组try{}catch{}finally{}最终抛出错误3。它们处在外层try块内,所以这个错误被外层try对应的catch捕捉,并被最终输出:3。

另外一个特殊情形是当运行在函数里的时候,如果finally返回了一个值,try或catch块内抛出的错误将同样被忽略:

function f() {
    try {
        throw 1; // 抛出错误
    } catch (e) {
        throw 2; // 捕获了错误 抛出了一个新错误
    } finally {
        return 3; // 返回了一个值
    }
}
console.log(f()); // 输出3

上面这个例子里,catch块内抛出了一个错误,在一般情况下,如果finally块内没有返回值,错误将在执行完finally块后被层层抛出,直到被输出到控制台(没有被捕捉),而函数的运行将中断,所以也不会输出任何值。但是finally块内返回了一个值,所以catch块内的错误被忽略了,返回值3被正常输出,控制台看不到任何错误信息。

▉ 总结

  • 任何错误的抛出都会导致程序在原位置的运行立刻停止。
  • try、catch、finally块内都可以再嵌套try、catch、finally的组合。
  • 当错误被抛出时,程序会在该位置立刻停止运行,然后层层上溯,直到发现自己被包裹在一个try块里,而把该错误传递到其相应的catch块,然后在此接着运行,如果其没有对应的catch块,将在运行完finally块后继续上溯,直到被捕捉或到达代码顶端而被输出到控制台。
  • finally块内的返回值或抛出的错误将覆盖任何原有的错误。

▉ 应用

我们已经有了在JavaScript中处理异常的办法,那么,应该怎样使用呢?

■ 首先想到的用法:流程控制(应当禁止)

说实话,当我看到throw与catch默契的关系的时候,第一时间不是想到用来处理错误,而是用它来进行流程控制,“多么完美啊!”,我只需要这样写:

try {
    // 一些代码
    if (情况1) throw 1;
    // 一些代码
    if (情况2) throw 2;
    // 一些代码
    throw 3;
} catch (code) {
    if (code instanceof Error) {
        throw (code);
    }
    switch (code) {
    case 1:
    case 2:
    case 3:
    }
}

好像还是很美的,我甚至还考虑到假如try块真的出现错误:只要把它抛出。

可事实是,即使有这种需求,就算使用匿名函数也并不比它麻烦多少,甚至或许还更简单:

switch ((function() {
    // 一些代码
    if (情况1) return 1;
    // 一些代码
    if (情况2) return 2;
    // 一些代码
    throw 3;
}
)()) {
case 1:
case 2:
case 3:
}

尤其它不会打乱原有的错误处理逻辑。

■ 最暴力直接的用法(不推荐)

看来try...catch最好还是用来处理错误,但是显然,它不是用来在开发阶段处理bug的工具,因为我们有更好的debugger和开发者工具。

事实上,回想我不长的代码生涯,我都是这样用的:

有些时候,我们想运行一段代码,但是我们知道这段代码很可能会出错,而我又不希望它影响接下来的步骤,也就是说,我想运行一段代码,即使失败了也没事儿。如此,这样写显然是最简单的:

try{
  // 一段无关紧要的代码
}catch(e){}

也许你会发现这样写也能运行:

try{
  // 一段无关紧要的代码
}catch{}

能省则省嘛,但是它在IE上会报错,可能因为它并不符合标准,所以尽量还是写上吧。另外,参数是不能省略的,即使是在chrome上。

这样写也不行:

try{
  // 一段无关紧要的代码
}finally{}

因为没有catch,错误没有捕捉,仍旧会往上抛。

用这种方法来运行“一段无关紧要的代码”时,应该确定该内容真的无关紧要,以至于它发生任何错误都可以忽视它。

所以,也许我们应该再保险一点,虽然它无关紧要,但还是把这个错误记录下来:

try{
  // 一段无关紧要的代码
}catch(e){console.warn("也许无关紧要的",e)}

也可以用console.error或者console.log,更多可参阅 Console - MDN 。

即便如此,这样做似乎仍然显得不那么好,我们也许应该对错误做出更精确的判断和处理。

■ 基本用法的思考

我已经知道“错误处理”就应当用来处理错误,但是,错误不应该是在开发阶段就处理掉的吗?我们应该考虑到可能遇到的各种情形,尽量不出错误。

可现实是,各种环境等因素总是在不停地发生变化,而我们的代码不是总能预知,我们确实有暂时容忍错误存在的需求,或者有时候,容忍错误存在在一定程度上是简化我们代码的一种方法(应该适度),再或者说,即使部分功能出现错误,但是其他功能还是得继续运行。

总而言之,错误处理是我们把错误考虑进程序的一种方法。

错误具体应该怎样处理呢?

我们可以首先把错误分为两种:

  • 已预知的错误(已知错误)
  • 未预料的错误(未知错误)

然后可以按照意义分类:

  • 程序可以处理的
  • 程序不能处理的

这两种分类不完全重合,但存在相当的对应关系。

而针对错误,抛出与不抛出的区别在于:

  • 抛出错误表示程序必须中断
  • 不抛出错误表示程序还可以运行

具体应该采取什么样的做法要视情况而定,比如程序的重要程序、精密程度,程序之间的相关性,当然,还有具体的逻辑关系。

大多数时候,我觉得应该秉承这样的原则:

  1. 抛出不能处理的错误。
  2. 通知未知的错误(包括抛出、输出、记录等)。
  3. 程序应当中断时就中断。

基于这样的原则,我们可以根据实际情况选择抛出部分或者不抛出。

下面是一些示例(非范例或实例)

【例①】
try{
  // 一段代码
}catch(error){
  if(error instanceof ReferenceError){
    // 预料到的错误 做出一些处理
    // 尽管这种错误类型是我们预料到的 但是仍不排除有其他可能同样触发了这个错误
    // 或者尽管这种错误预料到了 但仍需要告知
    console.warn("Expected",error);
  }else{
    // 未预料的 抛出
    throw error;
  }
}
// 如果try块内没有发生错误,或者发生的错误是预料到的,将继续往下运行
// 否则,如果catch块抛出错误,将不会往下运行
// 一些代码;


【例②】
// 此例仅提供意会的示例,现实不一定应该这样写
// 似乎前端的错误处理必要程度还是比较低的

function weakButDispensable(){} // 容易发生错误但可有可无的
function essential(){} // 必要的
function important(){} // 重要的

try{
  weakButDispensable();
}catch(e){
  console.warn("**功能一如既往的发生了错误",e);
}

essential(); // 未在try块内 所以如果发生错误 程序将直接从此中断

try{
  important();
}catch(e){
  if(e instanceof somekindofError){
    // 如果发生这种错误 表示问题可以处理 不耽误后续执行
    console.error("**功能出错!",e); // 控制台输出
    ui.notice("**功能出了一些问题,请稍后尝试。"); // 告知用户
  }else{
    // 否则 不能处理 将抛出错误
    // 执行将中断 不会执行[后续代码]
    throw e;
  }
}

// 一些[后续代码]
console.log("end");

■ 创建自己的错误

一般情况下,前端代码复杂度应该不会很高,我觉得没有必要抛出自定义的错误或其他值,而用流程控制和函数返回值等等代替,因为感觉会在某种程度上增大错误处理的复杂度。但是现在JS也能写后端了,而且即使是前端,可能工程性较强或者对错误处理有特殊需求的时候也需要用到。

这个时候我们可以通过继承Error来创建自己的错误:

class MyError extends Error {
    constructor(...args) {
        super(...args);
        // name属性对Error对象比较重要,默认为“Error”,此处会自动修改为相应的类名
        this.name = this.constructor.name; 
    }
}

throw new MyError("some message");

Error对象有三个比较重要的属性:

  • name 表示错误的类型,如内置错误类型"TypeError",或者我们自定义的"MyError"。
  • message 表示错误信息,如“Cannot read property '***' of undefined”。
  • stack 是错误的追踪堆栈信息,创建Error对象的时候会自动创建。

可以多重继承,对自定义的错误进行分类:

// 以下 我尝试写了一个自定义错误的继承机制
// 注释没有写的很详细
// 想写一个规范的注释,看注释规范啥的也没看太明白,随便写了一下

/* 我自己的错误类
 * 继承自Error 此后所有自定义错误都继承MyError
 * @constructor
 * @param {string|object} 详细信息 可以只写字符串 表示Error的message,可以是一个对象 用来包含各种相关信息(如message)
**/
class MyError extends Error {
    constructor(arg) {
        super();

        // 自动设置name属性
        this.name = this.constructor.name;

        // 写入详细信息 this.details
        this.details = {
            ...{
                message: ""
            },
            ...((arg instanceof Object) ? arg : ({
                message: (arg!==undefined)?String(arg):""
            }))
        }

        // 设置 message 属性 (包含入关于相应错误类别的特定信息 errorTypeMessage)
        let _t = [];
        if (this.errorTypeMessage)
            _t.push(this.errorTypeMessage);
        if (this.details.message)
            _t.push(this.details.message);
        this.message = _t.join("n");
    }
}

// 继承MyError
class ValidationError extends MyError {
}

// 继承ValidationError 
class PropertyRequiredError extends ValidationError {
    get errorTypeMessage() {
        return `property required: ${this.details.requiredPropertyName || ""}`;
    }
}

throw new PropertyRequiredError({
    message: "需要用户名啊啊啊啊",
    requiredPropertyName: "username"
});

throw new ValidataionError("验证错误");

以上按照自己的理解和思考,看了一些别人写的,大概写了这么个东西出来,以后学到更多东西肯定还会对很多东西再改动,可能会再回来改。

■ 异步编程下的错误处理

try{}catch{}只能捕获同步编程下的错误,对于异步编程时可能比较麻烦。

倒是看了一些NodeJS错误处理的文章,但是这一块等以后补吧,等再巩固了Promise异步编程啥的知识,了解了一些后端JS的编程之后。

■ 更多

可能忘了还有一些其他东西没写,但是好累啊。。。

还是得换种写法,感觉自己像唠叨的老太太,同一件事情反复说,还是得精简一点,写成这样没有意义,浪费时间,给自己看就够了,不能老觉得是给别人看的。

下次的宗旨:

  • 都覆盖到
  • 道理清晰,看得懂
  • 文字精炼,一目了然
  • 写得快,学习占大头,整理占小头

另外感觉就算写这个也还是会忘,可能记性不太好。。。弄完之后还是得尽快进入到实战,再回头翻,再熟练。

参考

  1. ^throw - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/throw
  2. ^try...catch - MDN https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/try...catch
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 C++ 中,异常处理使用 `try`、`catch` 和 `throw` 关键字来实现。 `try` 块包含可能引发异常的代码。如果在 `try` 块内发生异常,则会跳转到 `catch` 块。`catch` 块是用于处理异常的代码块。 下面是一个简单的例子,演示如何使用 `try` 和 `catch` 语句处理异常: ```cpp try { int x = 10; int y = 0; if (y == 0) { throw "除数不能为0"; } int result = x / y; cout << "结果是:" << result << endl; } catch (const char* msg) { cout << "发生异常:" << msg << endl; } ``` 在上面的代码中,我们首先定义了两个整数 x 和 y,然后检查 y 是否等于 0。如果 y 等于 0,则使用 `throw` 关键字抛出一个异常,其中包含一条错误消息。在 `catch` 块中,我们捕获并处理该异常,并打印错误消息。 除了 `catch` 块外,还可以使用 `finally` 块来完成异常处理。`finally` 块在 `try` 或 `catch` 块执行完毕后都会执行,无论是否发生异常。下面是一个包含 `finally` 块的例子: ```cpp try { int x = 10; int y = 0; if (y == 0) { throw "除数不能为0"; } int result = x / y; cout << "结果是:" << result << endl; } catch (const char* msg) { cout << "发生异常:" << msg << endl; } finally { cout << "程序执行完毕" << endl; } ``` 在上面的代码中,如果 y 等于 0,则会抛出一个异常。在 `catch` 块中,我们捕获并处理该异常,并打印错误消息。无论是否发生异常,都会在 `finally` 块中打印一条消息,表明程序执行完毕。 总的来说,`try`、`catch` 和 `throw` 关键字是 C++ 中非常重要的异常处理机制,可以帮助我们更好地处理代码中可能发生的异常情况。 ### 回答2: try catch throw 是 C 语言中处理异常的机制。 try 块用于包含可能出现异常的代码块,catch 块用于捕获并处理异常,throw 用于抛出异常。 在 try 块中,我们可以放置一段可能会引发异常的代码。当异常发生时,程序会立即跳转到最近的 catch 块进行处理。catch 块中可以通过制定异常类型来捕获指定类型的异常,也可以使用省略号来表示可以捕获任意类型的异常。 在 catch 块中,我们可以执行一些特定的操作来处理异常,比如打印错误信息、恢复现场、释放资源等。也可以选择继续抛出异常以便更高层的 catch 块继续捕获和处理。如果没有合适的 catch 块来处理异常,那么程序会终止执行。 throw 用于主动抛出异常。通过 throw,我们可以在代码中显式地抛出异常对象,而不需等待异常的自动发生。一个 throw 表达式后的执行路径会立即跳转到最近的 catch 块进行处理。在 catch 块中,我们可以选择是否继续抛出异常或者中止程序。 使用 try catch throw 可以帮助我们处理程序中的异常,使程序能更好地控制和处理各种可能的错误情况,提高程序的健壮性和可靠性。在 C 语言中,try catch throw 并不是原生支持的,但可以借助库函数或者自定义宏进行模拟实现。 ### 回答3: try catch throw 是一种在C语言中处理异常的机制。 在C语言中,异常处理是通过错误码返回来实现的。但是在一些特殊情况下,我们可能需要更加灵活和可控的异常处理方式。而try catch throw 机制提供了这样的功能。 try catch throw 结合了三个关键字,分别是trycatchthrowtrytry块用来包含可能会抛出异常的代码。当try块中的代码出现异常时,异常会被抛出。 catchcatch块用来捕获和处理try块中抛出的异常。catch块包含了对异常的处理代码,以便程序可以进行相应的处理操作。 throwthrow关键字用来在try块中主动抛出异常。当程序遇到throw关键字时,即可引发异常,并将其传递给catch块进行处理。 利用try catch throw机制,我们可以更好地控制代码的异常处理逻辑,提高程序的健壮性和可靠性。当程序发生异常时,可以通过catch捕获异常,并进行相应的处理。而且,由于throw关键字的存在,我们还可以在适当的时候主动抛出异常,以便在代码中进行异常处理。 总之,try catch throw机制是C语言中一种比较高级和灵活的异常处理方式。它可以帮助我们更好地管理和控制程序中的异常情况,提高代码的可靠性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值