继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

异步错误的获取

哔哔one
关注TA
已关注
手记 463
粉丝 93
获赞 543

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


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP