JavaScript异常错误处理指南

在前端的 JavaScript 开发中,发现开发者对于错误异常的处理普遍都比较简单粗暴,如果应用程序中缺少有效的错误处理和容错机制,代码的健壮性就无从谈起。本文整理出了一些常见的错误异常处理的场景,旨在为前端的 JavaScript 错误异常处理提供一些基础的指导。

Error 对象

先来简单介绍一下 JavaScript 中的 Error 对象,通常 Error 对象由重要的两部分组成,包含了 error.message 错误信息和 error.stack 错误追溯栈。

产生一个错误很简单,比如在 foo.js 中直接调用一个不存在的 callback 函数。

// foo.js
function foo () {
    callback();
}

foo();

此时通过 Chrome 浏览器的控制台会展示如下的信息。

Uncaught ReferenceError: callback is not defined
    at foo (foo.js:2)
    at foo.js:5

其中 Uncaught ReferenceError: callback is not defined 就是 error.message 错误信息,而剩下的 at xxx 就是具体的错误追溯栈,在 Chrome 的控制台中,对错误的展示进行了优化。

如果我们通过 window.onerror 来捕获到该错误后将 Error 对象直接输出到页面中会展示出更原始的数据。

<!-- 展示错误的容器 -->
<textarea id="error"></textarea>

// 输出错误
window.onerror = function (msg, url, line, col, err) {
    document.getElementById('error').textContent = err.message + '\n\n' + err.stack;
};

原始的错误数据中会展示出错误追溯栈中的 Source URL。

callback is not defined

ReferenceError: callback is not defined
    at foo (http://example.com/js-error/foo.js:2:5)
    at http://example.com/js-error/foo.js:5:1

有了错误追溯栈,就能通过发生错误的文件 Source URL 和错误在代码中的具体位置来快速定位到错误。

看起来好像很简单,但实际的开发中如何有效的捕获错误,如何有效的抛出错误都有一些需要注意的点,下面逐个的来讲解。

window.onerror

前端在捕获错误时都会通过绑定 window.onerror 事件来捕获全局的 JavaScript 执行错误,标准的浏览器在响应该事件时会依次提供 5 个参数。

window.onerror = function(message, source, lineno, colno, error) { ... }

  1. message 错误信息
  2. source 错误发生时的页面 URL
  3. lineno 错误发生时的 JS 文件行数
  4. colno 错误发生时的 JS 文件列数
  5. error 错误发生时抛出的标准 Error 对象

使用 window.addEventListener 也能绑定 error 事件,但是该事件函数的参数是一个 ErrorEvent 对象。

绑定 window.onerror 事件时,事件处理函数的第 5 个参数在低版本浏览中或 JS 资源跨域场景下可能不是 Error 对象。

在 Chrome 浏览器中如果页面加载的 JS 资源文件中存在跨域的 script 标签,在发生错误时会提示 Script error 而缺乏错误追溯栈。

window.onerror 在响应跨域 JavaScript 错误时缺乏错误追溯栈时的 arguments 对象如下:

[
    'Script error.',
    '',
    0,
    0,
    null
]

为了正常的捕获到跨域 JS 资源文件的错误,需要具备两个条件: 1. 为 JS 资源文件增加 CORS 响应头。 2. 通过 script 引用该 JS 文件时增加 crossorigin="anonymous" 的属性,如果是动态加载的 JS,可以写作 script.crossOrigin = true

window.onerror 能捕获一些全局的 JavaScript 错误,但还有不少场景在全局是捕获不到的。

try/catch

window.onerror 能捕获全局场景下的错误,如果已知一些程序的场景中可能会出现错误,这个时候一般会使用 try/catch 来进行捕获。

但是在使用 try/catch 块时无法捕获异步错误,例如块中使用了 setTimeout

try {
    setTimeout(function () {
        callTimeout();  // callTimeout 未定义,会抛错
    }, 1000);
}
catch (err) {
    console.log('catch the error', err); // 不会被执行
}

try/catch 在处理 setTimeout 这类异步场景时是无效的,执行时仍会抛错,catch 中的代码不会被执行。

虽然在 try/catch 中没有捕获到,此时如果有绑定 window.onerror 则会被全局捕获。

由此可见, try/catch 应该是只能捕获 JS Event Loop 中同步的任务。

如果想正确的捕获 setTimeout 中的错误,需要将 try/catch 块写到 setTimeout 的函数中。

setTimeout(function () {
    try {
        callTimeout(); // callTimeout 未定义,不会抛错
    }
    catch (err) {
        console.log('catch the error', err); // 将会被执行
    }
}, 1000);

Promise

Promise 有自己的错误处理机制,通常 Promise 函数中的错误无法被全局捕获。

var promise = new Promise(executor);
promise.then(onFulfilled, onRejected);

比较容易遗漏错误处理的地方有 executoronFulfilled ,在这些函数中如果发生错误都不能被全局捕获。

正确的捕获 Promise 的错误,应该使用 Promise.prototype.catch 方法,意外的错误和使用 reject 主动捕获的错误都会触发 catch 方法。

catch 方法中通常会接收到一个 Error 对象,但是当调用 reject 函数时传入的是一个非 Error 对象时,catch 方法也会接收到一个非 Error 对象,这里的 reject 和 throw 的表现是一样的,所以在使用 reject 时,最好是传入一个 Error 对象。

reject(
    new Error('this is reject message')
);

值得注意的是,如果 Promise 的 executor 中存在 setTimeout 语句时, setTimeout 的报错会被全局捕获。

Async Function

Async Function 和 Promise 一样,发生错误不会被全局的 window.onerror 捕获,所以在使用时如果有报错,需要手动增加 try/catch 语句。

匿名函数

匿名函数的使用在 JavaScript 中很常见,但是当出现匿名函数的报错时,在错误追溯栈中会以 anonymous 来标识错误,为了排查错误方便,可以将函数进行命名,或者使用函数的 displayName 属性。

函数如果有 displayName 属性,在错误栈中会展示该属性值,如果用于命名重要的业务逻辑属性,将有效帮助排查错误。

throw error

上面说了很多错误捕获的注意点,如果要主动的抛错,都会使用 throw 来抛错,常见的几种抛错方法如下:

throw new Error('Problem description.')  // 方法 1
throw Error('Problem description.')      // 方法 2
throw 'Problem description.'             // 方法 3
throw null                               // 方法 4

其中方法 1 和方法 2 的效果一样,浏览器都能正确的展示错误追溯栈。方法 3 和方法 4 不推荐,虽然能抛错,但是在抛错的时候不能展示错误追溯栈。

try/catchthrow ,一个用来捕获错误,一个用来抛出错误,如果两个结合起来用通常等于脱了裤子放屁多此一举,唯一有点用的是可以对错误信息进行再加工。

可以在 Chrome 控制台中模拟出一个结合使用的实际场景。

try {
    foo();
}
catch (err) {
    err.message = 'Catch the error: ' + err.message;
    throw Error(err);
}

由于在 catch 块中又抛出了错误,所以该错误没有被捕获到,但此时错误信息经过了二次封装。

Uncaught Error: ReferenceError: Catch the error: foo is not defined

通过对错误信息的二次封装,可以增加一些有利于快速定位错误的额外信息。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容