JavaScript错误处理机制详解


在ES3之前js代码执行的过程中,一旦出现错误,整个js代码都会停止执行,这样就显的代码非常的不健壮。

在Java或C#等一些高级语言中,都提供了异常处理机制,可以处理出现的异常,而不会停止整个应用程序。

从ES3开始,js也提供了类似的异常处理机制,从而让js代码变的更健壮,即使执行的过程中出现了异常,也可以让程序具有了一定的异常恢复能力。

Error对象

JavaScript 解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript 原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。

const err = new Error('我是好人');
console.log(err.message) // "我是好人"

上面代码中,我们调用Error() 构造函数,生成一个实例对象errError() 构造函数接受一个参数,表示错误提示,可以从实例的message属性读到这个参数。

JavaScript 语言标准只提到,Error 实例对象必须有message属性,表示出错时的提示信息,没有提到其他属性。大多数 JavaScript 引擎,对Error实例还提供namestack属性,分别表示错误的名称错误的堆栈,但它们是非标准的,不是每种实现都有。

  • message:错误提示信息(标准属性)
  • name:错误名称(非标准属性)
  • stack:错误的堆栈(非标准属性)

使用name和message这两个属性,可以对发生什么错误有一个大概的了解。

if (error.name) {
  console.log(error.name + ': ' + error.message);
}

stack属性用来查看错误发生时的堆栈。

function throwError() {
  throw new Error('小错要抛出来啦');
}

function catchError() {
  try {
    throwError();
  } catch(e) {
    console.log(e.stack); // print stack trace
  }
}

catchError()

// Error: 小错要抛出来啦
//    at throwError (<anonymous>:2:9)
//    at catchError (<anonymous>:7:5)
//    at <anonymous>:13:1

上面代码中,错误堆栈的最内层是throwError函数,然后是catchError函数,最后是函数的运行环境。

js内置错误类型

Error实例对象是最一般的错误类型,在它的基础上,JavaScript 还定义了6个派生对象。

SyntaxError 对象

SyntaxError对象是解析代码时发生的语法错误。

// 变量名错误
const 1aa;
// Uncaught SyntaxError: Invalid or unexpected token

// 缺少括号
console.log 'hi');
// Uncaught SyntaxError: Unexpected string

上面代码的错误,都是在语法解析阶段就可以发现,所以会抛出SyntaxError。第一个错误提示是“token 非法”,第二个错误提示是“字符串不符合要求”。

ReferenceError 对象

ReferenceError对象是引用一个不存在的变量时发生的错误。

// 使用一个不存在的变量
unknownVariable
// Uncaught ReferenceError: unknownVariable is not defined

另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果赋值。

// 等号左侧不是变量
console.log() = 1
// Uncaught ReferenceError: Invalid left-hand side in assignment

上面代码对函数console.log的运行结果赋值,结果引发了ReferenceError错误。

TypeError 对象

TypeError对象是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等基本数据类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。

new 123
// Uncaught TypeError: 123 is not a constructor

const obj = {};
obj.unknownMethod()
// Uncaught TypeError: obj.unknownMethod is not a function

上面代码的第二种情况,调用对象不存在的方法,也会抛出TypeError错误,因为obj.unknownMethod的值是undefined,而不是一个函数。

RangeError 对象

RangeError对象是一个值超出有效范围时发生的错误。主要有几种情况,一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值。

// 数组长度不得为负数
new Array(-1)
// Uncaught RangeError: Invalid array length

// Number对象的方法参数超出范围
const num = new Number(12.34)
num.toFixed(-1)
// Uncaught RangeError: toFixed() digits argument must be between 0 and 100

URIError 对象

URIError对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape() 这六个函数。

decodeURI('%2')
// Uncaught URIError: URI malformed

注: escape()unescape() 已废弃

EvalError 对象

eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。

总结

以上这6种派生错误,连同原始的Error对象,都是构造函数。开发者可以使用它们,手动生成错误对象的实例。这些构造函数都接受一个参数,代表错误提示信息(message)。

const err1 = new Error('出错了!');
const err2 = new RangeError('出错了,变量超出有效范围!');
const err3 = new TypeError('出错了,变量类型无效!');

err1.message // "出错了!"
err2.message // "出错了,变量超出有效范围!"
err3.message // "出错了,变量类型无效!"

一般情况,不同的错误,处理方式不一样。可以参考下面的处理方式。不过在实际开发中,大多数开发者并没有对错误类型进行进一步的处理。

try { 
  someFunction(); 
} catch (error){ 
  if (error instanceof TypeError){ 
    //处理类型错误 
  } else if (error instanceof ReferenceError){ 
    //处理引用错误 
  } else { 
      //处理其他的错误 
  }
}
new CustomError('哦,报错了')

throw 异常

抛出js内置错误类型的对象

在大部分的代码执行过程中,都是出现错误的时候,由浏览器(javascript引擎)抛出异常,然后程序或停止执行,或被try…catch 捕获。

然而有时候我们在检测到一些不合理的情况发生的时候也可以主动抛出错误。

使用 throw 关键字抛出来主动抛出异常。

throw new Error("看,报错了吧");
console.log("执行不到这里的")

注意:

  1. thow后面就是我们要抛出的异常对象。在以前的时候都是出现错误的时候浏览器抛出异常对象,只是现在是我们自己主动抛出的异常对象。
  2. 只要有异常对象抛出,不管是浏览器抛出的,还是代码主动抛出,都会让程序停止执行。如果想让程序继续执行,则有也可以用try…catch来捕获。
  3. 每一个错误类型都可以传入一个参数,表示实际的错误信息。
  4. 我们可以在适当的时候抛出任何我们想抛出的异常类型。throw new SyntaxError(“语法错误…”);

看下面的代码:

/*该函数接收一个数字,返回他的平方。*/
function foo(num) {
    if(typeof num == "number"){
        return num * num;
    }else{
        throw new TypeError("类型错误,你应该传入一个数字...")
    }
}
console.log(foo(33))
console.log(foo("abc"))

throw可以抛出任何类型的值。也就是说,它的参数可以是任何值。

// 抛出一个字符串
throw 'Error!';
conole.log('看看能不能执行到这里')
// Uncaught Error!

// 抛出一个数值
throw 42;
conole.log('看看能不能执行到这里')
// Uncaught 42

// 抛出一个布尔值
throw true;
conole.log('看看能不能执行到这里')
// Uncaught true

// 抛出一个对象
throw {
  toString: function () {
    return 'Error!';
  }
};
conole.log('看看能不能执行到这里')
// Uncaught {toString: ƒ}

抛出自定义类型的错误对象

我们不仅仅可以抛出js内置的错误类型的对象,也可以自定义错误类型,然后抛出自定义错误类型的对象。
如果要自定义错误类型,只需要继承任何一个js错误类型都可以。一般直接继承Error即可。

function CustomError(message) {
    this.message = message ?? '默认错误消息提示'
    this.name = 'CustomError'
}

CustomError.prototype = new Error(); // 这句不写可以试试输出看
try {
    throw new CustomError("注意:这是自定义错误类型")
}catch (error){
    console.log(error.message)
}

上面代码自定义一个错误对象CustomError,让它继承Error对象。然后,就可以生成这种自定义类型的错误了。

异常捕获

基本的try…catch语句

ES3开始引入了 try-catch 语句,是 JavaScript 中处理异常的标准方式。

语法:

try{ 
  //可能发生异常的代码 
}catch(error){ 
  //发生错误执行的代码 
}

看下面的代码:

try{
    console.log(b);
    console.log("我不会输出的,不要找了")
}catch(error){
    console.log("发生错误了")
}
console.log("我try catch后面的代码")

说明:

  1. 把有可能出的问题的代码放在 try 语句中。try语句中可以理论上可以写任何的代码,只要有一行代码出现问题,整个程序的执行流程就会立即调到catch语句中执行。
  2. 一旦try中有一行代码发生异常,则这行出错代码的后面的try中的其他语句都不会再执行。比如上面代码中的console.log(b);这行代码会出错,则立即去执行catch中的代码。所以console.log(“我不会输出的,不要找了”)这行代码则不会再执行
  3. 在执行catch中的代码之前,js引擎会首先根据错误类型自动创建一个错误,并通过catch后面的参数传递到catch中。不同的浏览器创建的error对象不一样,但是他们都包含一个message属性,值是这个错误的一些信息。
  4. catch中的代码执行完毕之后,会继续执行后面的代码,程序不会停止下来。

finally语句

在 try…catch 中,try 中一旦出现错误则紧接着的js语句不会执行,如果不出现错误则 catch 中的语句不会执行。

Javascript 参考其他编程语言,也提供了一种 finally 语句:不管 try 中的语句有没有错误,在最后都会执行 finally 中的语句。

即:try 中语句不发生错误执行完毕后会执行 finally 中的语句,try 中的语句发生错误,则执行 catch中的语句,catch 中的语句执行完毕后也会执行 finally 中的语句。

语法:

try{

}catch(error){

}finally{

}
try{
    console.log(b);
    console.log("我不会输出的,不要找了")
}catch(error){
    console.log("发生错误了")
}finally{
    console.log("不管发生不发生错误,我都会执行")
}
console.log("我try catch后面的代码")

所以在 finally 中我们可以放置我们必须要执行的代码。

注意:

  1. 在js中,如果添加了 finally 语句,则 catch 语句可以省略。所以下面的代码也是正确的。
  2. 如果没有 catch 语句,则一旦发生错误就无法捕获这个错误,所以在执行完 finally 中的语句后,程序就会立即停止了。
  3. 所以,在实际使用中,最好一直带着 catch 语句。
try{
    console.log(b);
    console.log("我不会输出的,不要找了")
}finally{
    console.log("不管发生不发生错误,我都会执行")
}
console.log("我try catch后面的代码")

用例

function returnTest(x) {
  try {
    console.log(x);
    return 'result';
  } finally {
    console.log('FINALLY');
  }
}

const result = returnTest('hello')
console.log(result)
// hello
// FINALLY
// result

上面代码中,try代码块没有发生错误,而且里面还包括return语句,但是finally代码块依然会执行。而且,这个函数的返回值还是result。

下面的例子说明,return语句的执行是排在finally代码之前,只是等finally代码执行完毕后才返回。

let count = 0;
function countUp() {
  try {
    return count;
  } finally {
    count++;
  }
}

console.log(countUp())
// 0
console.log(count)
// 1

上面代码说明,return语句里面的count的值,是在finally代码块运行之前就获取了。

下面的例子充分反映了try…catch…finally这三者之间的执行顺序。

function f() {
  try {
    console.log(0);
    throw 'bug';
  } catch(e) {
    console.log(1);
    return true; // 这句原本会延迟到 finally 代码块结束再执行
    console.log(2); // 不会运行
  } finally {
    console.log(3);
    return false; // 这句会覆盖掉前面那句 return
    console.log(4); // 不会运行
  }
  console.log(5); // 不会运行
}

var result = f();
// 0
// 1
// 3

console.log(result) 
// false

上面代码中,catch代码块结束执行之前,会先执行finally代码块。

catch代码块之中,触发转入finally代码块的标志,不仅有return语句,还有throw语句。

function f() {
  try {
    throw '出错了!';
  } catch(e) {
    console.log('捕捉到内部错误');
    throw e; // 这句原本会等到finally结束再执行
  } finally {
    return false; // 直接返回
  }
}

try {
  const result = f();
  console.log(result)
} catch(e) {
  // 此处不会执行
  console.log('caught outer "bogus"');
}

//  捕捉到内部错误
// false

上面代码中,进入catch代码块之后,一遇到throw语句,就会去执行finally代码块,其中有return false语句,因此就直接返回了,不再会回去执行catch代码块剩下的部分了。

try代码块内部,还可以再使用try代码块。

try {
  try {
    consle.log('Hello world!'); // 报错
  } finally {
    console.log('Finally');
  }
  console.log('Will I run?');
} catch (error) {
  console.error(error.message);
}
// Finally
// consle is not defined

上面代码中,try里面还有一个try。内层的try报错(console拼错了),这时会执行内层的finally代码块,然后抛出错误,被外层的catch捕获。

合理使用try…catch

当 try-catch 语句中发生错误时,浏览器会认为错误已经被处理了,浏览器就不再报告错误了。这也是最简单的一种情况。

使用 try-catch 最适合处理那些我们无法控制的错误。假设你在使用一个大型 JavaScript 库中的 函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在 try-catch 语句当中,一有什么错误发生,也好可以恰当地处理它们。

在明明知道自己的代码会发生错误时,再使用 try-catch 语句就不太合适了。例如,如果传给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不应用使用 try-catch 语句。因为try…catch语句是比较耗资源的事情。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Promise 提供了一种统一的错误处理机制,可以使用 `.catch()` 方法来捕获和处理 Promise 中的错误。下面是一个简单的例子: ```javascript function fetchData() { return new Promise((resolve, reject) => { // 模拟异步操作 setTimeout(() => { const randomNumber = Math.random(); if (randomNumber < 0.5) { resolve('Data fetched successfully'); } else { reject(new Error('Error occurred while fetching data')); } }, 2000); }); } fetchData() .then((data) => { console.log(data); }) .catch((error) => { console.error(error); }); ``` 在上面的例子中,`fetchData` 函数返回一个 Promise 对象,并模拟了一个异步操作。如果随机生成的数字小于 0.5,则 Promise 的状态变为 resolved,并调用 `resolve` 方法,将数据成功返回。否则,Promise 的状态变为 rejected,并调用 `reject` 方法,传递一个错误对象。 在使用 Promise 的时候,可以使用 `.then()` 方法来处理 Promise 成功时的结果,使用 `.catch()` 方法来处理 Promise 失败时的错误。在上述例子中,`.then()` 方法用于处理成功的情况,打印出成功获取到的数据;`.catch()` 方法用于处理失败的情况,打印出错误信息。 这种错误处理机制使得代码更加清晰和集中化。如果发生了错误,它会被传递给最近的 `.catch()` 方法,从而避免了在每个回调函数中进行错误处理的繁琐和冗余。此外,还可以使用 `.finally()` 方法来执行无论 Promise 成功或失败都需要执行的代码块。 需要注意的是,如果在 Promise 链中没有显式的 `.catch()` 方法来处理错误,那么错误将被传递到全局的未捕获异常处理器(如 `window.onerror` 或 `unhandledrejection` 事件),从而可能导致应用程序崩溃。因此,建议始终使用 `.catch()` 方法来处理 Promise 中的错误,以保证代码的稳定性和可靠性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

定栓

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值