2021年1月5日(egg异常捕获)


1.try…catch

Node.js 是一个异步的世界,之前都是callback的形式的异步编程模型,会带来很多问题,在es8的时候发布了async function可以很好的解决回调地狱的问题,可以使用try…catch来捕获异常

async function onerror(ctx, next) {
  try {
    await next();
  } catch (err) {
    ctx.app.emit('error', err);
    ctx.body = 'server error';
    ctx.status = err.status || 500;
  }
}
2.ctx.runInBackground(scope)

如果 service.trade.check 方法中代码有问题,导致执行时抛出了异常,尽管框架会在最外层通过 try catch 统一捕获错误,但是由于 setImmediate 中的代码『跳出』了异步链,它里面的错误就无法被捕捉到了。因此在编写类似代码的时候一定要注意。

框架也考虑到了这类场景,提供了 ctx.runInBackground(scope) 辅助方法,通过它又包装了一个异步链,所有在这个 scope 里面的错误都会统一捕获。

class HomeController extends Controller {
  async buy () {
    const request = {};
    const config = await ctx.service.trade.buy(request);
    // 下单后需要进行一次核对,且不阻塞当前请求
    ctx.runInBackground(async () => {
      // 这里面的异常都会统统被 Backgroud 捕获掉,并打印错误日志
      await ctx.service.trade.check(request);
    });
  }
}
为了保证异常可追踪,必须保证所有抛出的异常都是 Error 类型,因为只有 Error 类型才会带上堆栈信息,定位到问题
3.中间件

在app文件下创建middleware文件夹,同时创建一个error_handler.js的文件

// app/middleware/error_handler.js
module.exports = (option, app) => {
    return async function errorHandler(ctx, next) {
      try {
        await next();
      } catch (err) {
        // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
        ctx.app.emit('error', err, ctx);
  
        const status = err.status || 500;
        // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
        const error = status === 500 && ctx.app.config.env === 'prod'
          ? 'Internal Server Error'
          : err.message;
  
        // 从 error 对象上读出各个属性,设置到响应中
        ctx.body = { error };
        if (status === 422) {
          ctx.body.detail = err.errors;
        }
        ctx.status = status;
      }
    };
  };

在config.default.js中配置

config.middleware = ['errorHandler'];
4.框架层统一异常处理

框架通过 onerror 插件提供了统一的错误处理机制。对一个请求的所有处理方法(Middleware、Controller、Service)中抛出的任何异常都会被它捕获,并自动根据请求想要获取的类型返回不同类型的错误(基于 Content Negotiation)。

  • errorPageUrl

    config/config.default.js

// config/config.default.js
module.exports = {
  onerror: {
    // 线上页面发生异常时,重定向到这个页面上
    errorPageUrl: '/50x.html',
  },
};
  • 自定义统一异常处理
// config/config.default.js
module.exports = {
  onerror: {
    all(err, ctx) {
      // 在此处定义针对所有响应类型的错误处理方法
      // 注意,定义了 config.all 之后,其他错误处理方法不会再生效
      ctx.body = 'error';
      ctx.status = 500;
    },
    html(err, ctx) {
      // html hander
      ctx.body = '<h3>error</h3>';
      ctx.status = 500;
    },
    json(err, ctx) {
      // json hander
      ctx.body = { message: 'error' };
      ctx.status = 500;
    },
    jsonp(err, ctx) {
      // 一般来说,不需要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式
    },
  },
};
  • 自定义 404 响应
    // app/middleware/notfound_handler.js
    module.exports = () => {
      return async function notFoundHandler(ctx, next) {
        await next();
        if (ctx.status === 404 && !ctx.body) {
          if (ctx.acceptJSON) {
            ctx.body = { error: 'Not Found' };
          } else {
            ctx.body = '<h1>Page Not Found</h1>';
          }
        }
      };
    };

    在配置中引入中间件:

    // config/config.default.js
    module.exports = {
      middleware: [ 'notfoundHandler' ],
    };

Author: wxy
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source wxy !
  TOC