一个拥有良好错误处理策略的应用程序对用户是很友好的,因此,作为开发者,应该非常清楚自己的代码什么情况下会失败,以及失败会导致什么结果,还需要理解各种捕获和处理JS错误的方式。

错误类型

我们需要知道,JS不管发生什么错误,都会抛出对应类型的错误对象,ECMA-262定义了一下8种错误类型

  • Error
  • InternalError
  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

Error

Error是基类,其他错误类型继承该类型。因此所有的错误类型都共享相同的属性,浏览器很少会抛出Error类型的错误,该类型主要用于开发者抛出自定义错误。

new Error([message[, fileName[,lineNumber]]])
  • message

    可选。人类可阅读的错误描述信息。

  • fileName

    可选。被创建的Error对象的fileName属性值。默认是调用Error构造器代码所在的文件 的名字。

  • lineNumber

    可选。被创建的Error对象的lineNumber属性值。默认是调用Error构造器代码所在的文件的行号。

当代码运行时的发生错误,会创建新的Error 对象,并将其抛出。

当像函数一样使用 Error 时 – 如果没有 new,它将返回一个 Error 对象。所以, 仅仅调用 Error 产生的结果与通过new 关键字构造 Error 对象生成的结果相同。

throw Error('error')
// has the same functionality
throw new Error('error')
console.log('不打印') // 打印不出来
// 需要使用throw操作符抛出,浏览器才能接收的到
// throw操作符必须有个值,但值的类型不限
// 如果没有捕获错误,抛出的错误会让代码立即停止执行

try/catch语句

当try/catch语句中发生错误时,浏览器会认为错误被处理了,便不会再报告错误。

该语句最好用在自己无法控制的错误上。

try {
    throw Error("error");
    console.log('inside')// 不会执行
} catch (err) {
    console.log(err);
}
console.log("continue")
// 使用try/catch语句,在try块中抛出错误时,代码会立即退出执行,跳到catch中进行处理,处理完毕会继续执行try/catch语句外部后续的代码
function testFinally() {
    try {
        return 1;
    } catch (error) {
        console.log(error);
        return 2;
    } finally {
        //   throw Error("error");如果抛出,无法被捕捉
        return 0;
    }
}
console.log(testFinally()) // 打印0
// try/catch语句中可选的finally子句始终运行

如果在finally里抛出错误便只能在外围进行捕捉

try {
    function testFinally() {
        try {
            return 1;
        } catch (error) {
            console.log(error);
            return 2;
        } finally {
            throw Error("error");
            return 0;
        }
    }
    console.log(testFinally());
} catch (error) {
    console.log(error);
}

InternalError

非标准: 该特性是非标准的,请尽量不要在生产环境中使用它!

该类型错误会在底层JS引擎抛出异常时由浏览器抛出.

示例场景通常为某些成分过大,例如:

  • “too many switch cases”(过多case子句);
  • “too many parentheses in regular expression”(正则表达式中括号过多);
  • “array initializer too large”(数组初始化器过大);
  • “too much recursion”(递归过深)。

EvalError

该类型的错误会在使用eval()函数发生异常时抛出。

RangeError

该类型的错误会在数值越界时抛出。试图传递一个number参数给一个范围内不包含该number的函数时则会引发RangeError

new Array(-20); //Uncaught RangeError: Invalid array length

我们可以通过原型链判断错误类型

try {
    new Array(-20);
} catch (error) {
    if (error instanceof RangeError) {
        console.log("true");
        // error handling
    }
}

ReferenceError

该类型错误会在找不到对象时发生

let obj = undefinedVariable;// Uncaught ReferenceError: undefinedVariable is not defined

SyntaxError

该类型错误会在JS语法错误的时候抛出

a++b // Uncaught SyntaxError: Unexpected identifier

TypeError

表示值的类型非预期类型时发生的错误。

let o = new 10();// Uncaught TypeError: 10 is not a constructor
console.log('name' in true)// Uncaught TypeError: Cannot use 'in' operator to search for 'name' in true
Function.prototype.toString.call('name')// Uncaught TypeError: Function.prototype.toString requires that 'this' be a Function

URIError

表示以一种错误的方式使用全局URI处理函数而产生的错误。

decodeURIComponent('%');// Uncaught URIError: URI malformed at decodeURIComponent (<anonymous>)

既然我们知道其他错误类型都是继承自Error的,那么我们便可以自己定义一种错误类型

function MyError(message) {
  Error.call(this)
  this.name = 'MyError';
  this.message = message || 'Default Message';
  this.stack = (new Error()).stack;
}
try {
  throw new MyError();
} catch (e) {
  console.log(e.name);     // 'MyError'
  console.log(e.message);  // 'Default Message'
}

error事件

任何没有被try/catch语句处理的错误都会在window对象上触发error事件,onerror事件处理程序需要使用DOMLevel0技术指定,因为它不遵循2的标准规范

throw 1;// Uncaught 1
window.onerror=(msg,url,line)=>{
    console.log(msg) // 无法捕捉
}
// 注意该事件要放在代码开头部分,不然就算抛出错误也无法捕捉的到
// 这相当于处理浏览器报告错误的最后一道防线,最好不要使用
window.onerror = (msg, url, line) => {
    console.log(msg);
};
window.addEventListener("error", function (event) {
    console.log(event);
});
new Array(-20);

图片也支持error事件。任何时候,如果图片的src属性中的URL没有返回可识别的图片格式,就会触发error事件

const img=new Image()
img.addEventListener('error',(e)=>{
    console.log(e)
})
img.src='yqx.png' // net::ERR_FILE_NOT_FOUND

错误处理策略

错误处理策略涉及很多错误和错误处理考量,包括日志记录和监控系统。这些主要是为了分析模式,以期找到问题的根源并了解有多少用户会受到错误影响。过去基本是在服务器上落地。

识别错误

借助静态代码分析器可以预先发现非常多的错误,静态代码分析器要求使用类型、函数签名及其他指令来注解JavaSript,以此描述程序如何在基本可执行代码之外运行。

分析器会比较注解和JS代码的各个部分,对在实际运行时可能出现的潜在不兼容问题给出提醒。

类型转换错误

主要原因是使用了会自动转换类型的操作符或语言构造。比如==、!=操作符,以及在if、for或while等流控制语句中使用非布尔值。

数据类型错误

常发生在将意外值传给函数的时候。

一般来说,用typeof检测原始类型的值,对象值用instanceof检测

通信错误

发生在与服务器之间通信的过程中

如URL格式或发送数据的格式不正确,在数据发送之前应该要使用encodeURIComponent()进行编码。

java.net.URLDecoder.decode(param, "UTF-8");// 后台接受参数并解码

区分错误

好的代码设计意味着应用程序某个部分的错误不会影响其他部分。

注意模块化开发的思想,模块初始化过程中的任何错误都不应该影响其他模块初始化。如果代码中由错误发生,则可以单独处理,并不会影响用户体验。

把错误记录到服务器中

Web应用程序开发中的一个常见做法是建立中心化的错误日志存储和跟踪系统。数据库和服务器错误正常写到日志中并按照常用API加以分类。

可以在服务器上建立入口处理JS错误

可以使用Image对象发送错误请求记录

function logError(sev,msg){
    let img = new Image()
    img.src = `log.php?sev=${encodeURIComponent(sev)}&msg=${encodeURIComponent(msg)}``
}

调试技术

通过console对象将js消息写入控制台,有下列方法

  • error()
  • info()
  • log()
  • warn()

控制台运行时

浏览器控制台就是read-eval-print-loop过程,与页面的JS运行并发。

在控制台的代码仍会受跨域限制和其他浏览器施加的规则约束。

控制台运行时也会继承开发者工具,提供JS上下文调试工具。

一个很有用的小工具比如点击某个元素,便可以使用$0访问该元素。

使用JS调试器

debugger关键字用于调用可能存在的调试功能。如果没有相关功能,这条语句会被简单跳过。