JS常见错误
当 JavaScript 引擎执行 JavaScript 代码时,会发生各种错误,常见的错误类型有
SyntaxError。
SyntaxError是解析代码时发生的语法错误
var 1a; //变量名错误 console.log 'hello'); // 缺少括号
ReferenceError
ReferenceError是引用一个不存在的变量时发生的错误。
比如在函数中调用一个变量,但是这个变量不存在的时候TypeError
TypeError是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用new命令,就会抛出这种错误,因为new命令的参数应该是一个构造函数。
var obj = {}; obj.handle() //obj.handle is not a function
还有一些这里就不一一介绍了。
try catch
使用
总之,我们在写代码的时候,程序可能遇到无法预测的异常情况而报错,从而阻塞代码执行,例如,网络连接中断,读取不存在的文件等。
上面的错误如果从产生的阶段上来划分的话,可以分成编译阶段和执行阶段的错误。
对于执行阶段的错误,我们通常使用try catch来捕获这种错误并处理它。
当代码块被try { ... }包裹的时候,一旦发生错误,就不再继续执行后续代码,转而跳到catch块。catch (e) { ... }包裹的代码就是错误处理代码,变量e表示捕获到的错误。
try{ console.log('step1'); var s = null; console.log(s.length) //产生错误 console.log('step2'); } catch(e){ console.log('error'); }//outputstep1 error
抛出错误
程序也可以主动抛出一个错误,让执行流程直接跳转到catch块。抛出错误使用throw语句。
实际上,JavaScript允许抛出任意对象,包括数字、字符串。但是,最好还是抛出一个Error对象。
catch捕获
JavaScript有一个标准的Error对象表示错误,还有从Error派生的TypeError、ReferenceError等错误对象。
我们在处理错误时,可以通过catch(e)捕获的变量e访问错误对象。我们抛出的错误其实是继承在这个Error对象。我们在throw的new Error传的参数就是error实例的message属性。
错误传播
如果在一个函数内部发生了错误,它自身没有捕获,错误就会被抛到外层调用函数,如果外层函数也没有捕获,该错误会一直沿着函数调用链向上抛出,直到被JavaScript引擎捕获,代码终止执行。
try { try{ throw new Error('这是抛出的错误'); } catch(e) { console.log('内层捕获的错误'); } } catch(e) { console.log('外层捕获的错误'); }//output内层捕获的错误
finally
最后,无论有没有错误,finally一定会被执行。
异步错误
如果try模块里面是通过异步操作抛出的异常,异常就不能正常捕获到。比如:
try{ setTimeout(()=>{ throw new Error('fail'); },1000); } catch (e){ console.log(e); }
这是因为异步调用是立即返回的,因此当发生异常的时候,已经脱离了try..catch..的上下文了,所以异常无法被捕获。
异步错误的解决方案
异步代码内部直接捕获错误
如果是异步的异常,那就在异步代码或者回调函数里捕获异常。
setTimeout(()=>{ try{ throw new Error('fail'); }catch (e){ console.log(e); } },1000);
使用Promise
var p1 = function(){ return new Promise(function(resolve,reject){ throw new Error('p1_同步_err'); //代码1 setTimeout(()=>{ console.log('p1执行') resolve(true) // throw new Error('p1_异步_err'); //代码2 // reject('p1_rej') //代码3 },1000) }) }var p2 = function(){ return new Promise(function(resolve,reject){ // throw new Error('p2_同步_err'); //代码4 setTimeout(()=>{ console.log('p2执行') // throw new Error('p2_异步_err'); //代码5 // reject('p2_rej') //代码6 },1000) }) } p1().then(p2).catch(function(err){ console.log('catch里的错误') console.log(err) })//output1catch里的错误 a.html:44 Error: p1_同步_err at a.html:23 at new Promise (<anonymous>) at p1 (a.html:22) at a.html:42 //output2 p1执行 a.html:27 Uncaught Error: p1_异步_err at setTimeout (a.html:27) //output3 p1执行 a.html:43 catch里的错误 a.html:44 p1_rej
做了一组试验。六种情况。比如
情况1,代码1执行,其它的代码2-6都注释掉。
情况2,代码2执行,其它的代码1,3-6都注释掉。
情况3,代码3执行,其它的代码1,2,4-6都注释掉。
结论:对于promise而言
如果是同步执行,throw出去的错误可以捕获,而异步执行的不可以
如果是异步执行,throw出去的错误不可以被捕获,只能通过reject来传递。
使用async和await
async function doSomething() { return new Promise((resolve, reject) => { setTimeout(() => { throw new Error("fail"); }, 1000); }); }async function main() { try { await doSomething(); } catch (e) { console.log(e.message); } } main();
效果类似。因为async await实现方式本身也是基于generator+promise
浏览器环境
在浏览器中,很多时候出现Error,整个页面就挂掉了。在window对象下有个 属性。我们可以给window.属性加上一个回调来实现对前端代码的监控。函数会在页面发生js错误时被调用。
window.可以拿到出错的信息以及文件名、行号、列号,如果在函数中return true可以让浏览器不输出错误信息到控制台。
否则会在控制台中显示错误消息。
nodeJs环境
需要说明的是,在node中大多数的异步方法都接受一个 callback 函数,该函数会接受一个 Error 对象传入作为第一个参数。 如果第一个参数不是 null 而是一个 Error 实例,则说明发生了错误,应该进行处理。
const fs = require('fs'); fs.readFile('一个不存在的文件', (err, data) => { if (err) { console.error('读取文件出错!', err); return; } // 否则处理数据 });
uncaughtException
uncaughtException 其实是 NodeJS 进程的一个事件。如果进程里产生了一个异常而没有被任何Try Catch捕获会触发这个事件。
NodeJS 对于未捕获异常的默认处理是:沿着代码调用路径反向传递回事件循环 - 触发 uncaughtException 事件 - 如果 uncaughtException 没有被监听,那么 - 打印异常的堆栈信息 - 触发进程的 exit 事件。Node.js原生提供uncaughtException事件挂到process对象上,用于捕获所有未处理的异常。
process.on('uncaughtException', function (err) { console.log('uncaughtException error'); });try { setTimeout(function(){ throw new Error('fail'); },1000); } catch (e) { console.log("catch error") }//outputuncaughtException error
uncaughtException
需要注意,如果打算使用 'uncaughtException' 事件作为异常处理的最后补救机制,这是非常粗糙的设计方式。未处理异常本身就意味着应用已经处于了未定义的状态。如果基于这种状态,尝试恢复应用正常进行,可能会造成未知或不可预测的问题。
而且uncaughtException错误会导致当前的所有的用户连接都被中断,甚至不能返回一个正常的 HTTP 错误码,这是由于uncaughtException 丢失了当前环境的堆栈,导致 Node 不能正常进行内存回收,比如下面的例子
app.get('/', function (req, res) { setTimeout(function () { throw new Error('async error'); res.send(200); }, 1000); }); process.on('uncaughtException', function (err) { res.send(500); // 做不到,拿不到当前请求的 res 对象});
domain
如果可以通过某种方式来捕获回调函数中的异常,那么就不会有uncaughtException 错误导致的崩溃。为了解决这个问题,Node 0.8 之后的版本新增了domain 模块,它可以用来捕获回调函数中抛出的异常。
domain模块,把处理多个不同的IO的操作作为一个组。注册事件和回调到domain,当发生一个错误事件或抛出一个错误时,domain对象会被通知,不会丢失上下文环境,也不导致程序错误立即退出。
domain 主要的 API 有 domain.run 和 error 事件。通过 domain.run 执行的函数中引发的异常都可以通过 domain 的 error 事件捕获,例如:
var app = express();var server = require('http').createServer(app);var domain = require('domain'); app.use(function (req, res, next) { var reqDomain = domain.create(); reqDomain.on('error', function (err) { // 下面抛出的异常在这里被捕获 res.send(500, err.stack); // 成功给用户返回了 500 }); reqDomain.run(next); }); app.get('/', function () { setTimeout(function () { throw new Error('async exception'); // 抛出一个异步异常 }, 1000); });
上面的代码将 domain 作为一个中间件来使用,保证之后 express 所有的中间件都在domain.run 函数内部执行。这些中间件内的异常都可以通过 error 事件来捕获。我们可以正常的给用户返回 500 错误。
所以,我们可以结合两种异常捕获机制,用 domain 来捕获大部分的异常。对于剩下的异常,通过 uncaughtException 事件来避免服务器直接 crash。
作者:Sunny_杰少
链接:https://www.jianshu.com/p/e290c5f7274e