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');
}
//output
step1
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)
})
//output1
catch里的错误
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对象下有个 onerror 属性。我们可以给window.onerror属性加上一个回调来实现对前端代码的监控。onerror函数会在页面发生js错误时被调用。
window.onerror可以拿到出错的信息以及文件名、行号、列号,如果在onerror函数中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")
}
//output
uncaughtException 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。
网友评论