异步错误的获取

作者: Sunny_杰少 | 来源:发表于2018-06-25 11:59 被阅读13次

    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。

    相关文章

      网友评论

        本文标题:异步错误的获取

        本文链接:https://www.haomeiwen.com/subject/putyyftx.html