错误捕获

在koa中,我们的错误捕获是通过try...catch实现的,捕获的是next函数,在中间件中next就是下一个中间件触发回调,如果next运行出错了,那么就可以被try...catch捕获。

但是,这种捕获,只能捕获同步形式的代码,如果有异步处理,那么try...catch就无法捕获到了。

比如:

try {
  setTimeout(() => {
    throw new Error("出错了")
  }, 1000)
} catch (error) {
  console.log("error")
};

按道理,如果能捕获错误,那么最终输出的是一个字符error,但是实际上输出的是错误对象 。

原因就是因为try...catch无法捕获异步的错误。

那么如何捕获异步的错误?

解决方法就是将异步的方法改用pormise对象封装,然后通过async和await转为同步,这样就可以通过try...catch捕获了。

(async function () {
  try {
    await test();
  } catch (error) {
    console.log("error")
  };
})()


function test() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error("出错了"));
    }, 1000)
  })
}

最后输出的是字符串error

总结:

koa中,错误的捕获,是通过try...catch方法,但是如果有异步的操作,需要转为promise,然后async和await转为同步,这样就能整个全局捕获错误了。

全局错误处理

创建一个middlewares文件夹,意思是中间件,文件夹存放于项目根目录,在middlewares目录下创建一个catchError.js文件,用于全局捕获错误。

catchError.js

const catchError = async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    ctx.body = "出现错误了"
  }
}


module.exports = catchError;

在app.js中引用:

const Koa = require("koa");
const InitManager = require("./core/init");
const bodyParser = require("koa-bodyparser");
const catchError = require("./middlewares/catchError");

//koa实例
const app = new Koa();




//全局错误捕获
app.use(catchError);
//body中间件解析
app.use(bodyParser());
//自动注册路由
InitManager.initCore(app);


app.listen(8080);

当我们某个中间件抛出错误时,便会被catchError中间件捕获。

错误的处理

错误分两种:

  1. 已知错误
  2. 未知错误

已知错误时我们能预先判断到的错误,比如参数校验不通过,未知则是由不知道哪里的代码所产生的错误。

那么处理已知错误,我们可以自己对Error对象进行调整,然后判断。

比如:

const Router = require("koa-router");
const router = new Router();


router.post("/test1/:id", (ctx, next) => {

  const error = new Error("测试错误");
  error.errorCode = 10001;
  error.status = 400;
  error.requestUrl = `${ctx.method}  ${ctx.path}`;
  throw error;

  const path = ctx.params;
  const query = ctx.request.query;
  const body = ctx.request.body;
  const header = ctx.request.header;

  ctx.body = "接受了";

});


module.exports = router;

我们手动new出一个error对象,然后给他添加一些自定义的属性,最后throw抛出。

在正式的编程环境中,是建议使用throw抛出错误的,因为只要是错误,就应该抛出,而不是return,抛出是可以被捕获到的。

抛出错误对象后,我们捕获代码就可以这样写:

const catchError = async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    if (error.errorCode) {
      ctx.body = {
        code: error.errorCode,
        msg: error.message,
        url: error.requestUrl
      };
      ctx.status = error.status;
    } else {
      ctx.body = "出现未知错误了"
    }
  }
}


module.exports = catchError;

当我们post这个api地址,就可以得到如下内容:

{
    "code": 10001,
    "msg": "测试错误",
    "url": "POST  /test1/33"
}

但是每次抛出一个错误,总是等于等于等于,赋值好几次,太麻烦了,我们要想个省事的办法。

封装创建错误对象的方法

通过class创建一个通用的抛出错误方法,继承Error对象。

core文件夹下创建一个http-error.js文件,代码如下:

class HttpError extends Error {
  constructor(msg = "服务器异常", errorCode = 10000, status = 400) {
    super();
    this.errorCode = errorCode;
    this.status = status;
    this.msg = msg;
  }
};


module.exports = { HttpError };

创建HttpError类,基于Error对象,接受三个参数,默认的msg信息,错误code,http状态码status。

使用的时候引入这个类并new出来,传入对应参数即可。

const Router = require("koa-router");
const router = new Router();
const { HttpError } = require("../../../core/http-error");

router.post("/test1/:id", (ctx, next) => {
  throw new HttpError("测试错误", 10001, 400);

  const path = ctx.params;
  const query = ctx.request.query;
  const body = ctx.request.body;
  const header = ctx.request.header;

  ctx.body = "接受了";

});


module.exports = router;

如果需要url参数,我们不需要在error对象里面填入属性了,我们本身的全局捕获错误就是个中间件,他就有ctx对象,直接通过这个ctx对象即可获取,于是代码改动后如下:

const { HttpError } = require("../core/http-error");

const catchError = async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    if (error instanceof HttpError) {
      ctx.body = {
        code: error.errorCode,
        msg: error.msg,
        url: `${ctx.method}  ${ctx.path}`
      };
      ctx.status = error.status;
    } else {
      ctx.body = "出现未知错误了"
    }
  }
}


module.exports = catchError;

对于是否已知错误的判断,也不用去判断他有没有含有这个属性,我们直接通过instanceof判断它是否奴属于HttpError对象即可,当然,判断前需要引入HttpError对象。

这样的话,我们以后创建Error对象,就直接引入HttpError并new出来,传入参数即可。

但是这样,也比较麻烦,每次都要引入,我们可以将它保存到全局global对象上,在最开始就挂载上去。

在node环境中,有一个全局对象global,浏览器是window

全局挂载HttpError

我们在init.js中,也就是初始化的时候挂载

init.js

const Router = require("koa-router");
const requireDirectory = require("require-directory");


class InitManager {
  //入口方法
  static initCore(app) {
    InitManager.app = app;
    InitManager.loadHttpError();
    InitManager.initLoadRouters();
  };
  //注册路由
  static initLoadRouters() {
    const apiDirectory = `${process.cwd()}/app/router`;
    requireDirectory(module, apiDirectory, {
      visit: (router) => {
        if (router instanceof Router) {
          InitManager.app.use(router.routes());
        }
      }
    });
  };
  //挂载HttpError
  static loadHttpError() {
    const HttpError = require("./http-error");
    global.err = HttpError;
  }
};

module.exports = InitManager;

这样global的err属性就是HttpError对象了。

使用的时候直接new出全局的这个即可。

throw new global.err.HttpError("测试错误", 10001, 400);

从某种角度来说并不建议使用全局,第一是要熟悉所有的错误对象类,当错误对象我们自定义的多了,熟悉的人可能知道怎么调用,不熟悉的,如果写错了,都不知道哪里的问题,第二是因为js在new这些类的时候,哪怕类名不存在,也不会报错,所以是建议手动引入。

自定义错误对象补充

在抛出的错误对象中,肯定会有很大一部分是重复的内容,比如参数校验的,不能为空啊,正整数啊,这些,报错的内容以及错误码都是相同的,这种就可以复用,我们可以继承HttpError,固定内容。

//基础错误对象
class HttpError extends Error {
  constructor(msg = "服务器异常", errorCode = 10000, status = 400) {
    super();
    this.errorCode = errorCode;
    this.status = status;
    this.msg = msg;
  }
};

//固定错误对象
class TestError extends HttpError {
  constructor(msg, errorCode) {
    super();
    this.errorCode = errorCode || 10000;
    this.status = 400;
    this.msg = msg || "测试报错";
  }
};



module.exports = { HttpError, TestError };

创建了一个简单的示例错误对象,继承HttpError,接收两个参数,参数是可以不传的,我们默认就预设了一些可用信息,为了省事,用起来就可以这样。

const Router = require("koa-router");
const router = new Router();
const { TestError } = require("../../../core/http-error");

router.post("/test1/:id", (ctx, next) => {
  throw new TestError();

  const path = ctx.params;
  const query = ctx.request.query;
  const body = ctx.request.body;
  const header = ctx.request.header;

  ctx.body = "接受了";

});


module.exports = router;

这样用起来就比较简单,复用性也强。

未知错误处理

已知错误我们使用了自定义错误对象,对于未知的,我们也应该返回一个相同结构的对象。

const { HttpError } = require("../core/http-error");

const catchError = async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    if (error instanceof HttpError) {
      ctx.body = {
        code: error.errorCode,
        msg: error.msg,
        url: `${ctx.method}  ${ctx.path}`
      };
      ctx.status = error.status;
    } else {
      ctx.body = {
        code: 9999,
        msg: "服务器发生错误",
        url: `${ctx.method}  ${ctx.path}`
      };
      ctx.status = 500;
    }
  }
}


module.exports = catchError;

我们肯定不能原样返回error对象,所以可以使用一个特殊的错误代码,信息就自定义了,然后status为500,而500本身就是表示服务发生错误。

这样就行了,相对来说比较简单的。

开发环境和正式环境错误处理

目前,我们所有的错误都被全局错误处理捕获到了,但是也正因为如此,导致我们后端没办法具体的看到错误的代码信息,所以我们要针对不同的环境做出错误处理。

一般就两种环境:

  1. 开发环境:development
  2. 正式环境:production

判断这两个环境,我们就需要用到config.js了。

在根目录创建一个config/config.js文件。

module.exports = {
  //dev、prod
  env: "dev"
};

导出这个对象,js中配置对象就很简单了,就是个键值对

我们判断,如果env的值为dev,说明这是一个开发环境,如果为prod则是正式环境。

全局挂载配置文件

init.js

const Router = require("koa-router");
const requireDirectory = require("require-directory");


class InitManager {
  //入口方法
  static initCore(app) {
    InitManager.app = app;
    InitManager.loadConfig();
    InitManager.loadHttpError();
    InitManager.initLoadRouters();
  };
  //注册路由
  static initLoadRouters() {
    const apiDirectory = `${process.cwd()}/app/router`;
    requireDirectory(module, apiDirectory, {
      visit: (router) => {
        if (router instanceof Router) {
          InitManager.app.use(router.routes());
        }
      }
    });
  };
  //挂载HttpError
  static loadHttpError() {
    const HttpError = require("./http-error");
    global.err = HttpError;
  };
  //挂载配置config
  static loadConfig() {
    const configPath = `${process.cwd()}/config/config.js`;
    const config = require(configPath);
    global.config = config;
  }
};

module.exports = InitManager;

也挂载到全局global对象上,因为config使用会很频繁。

全局错误捕获处理

catchError.js

const { HttpError } = require("../core/http-error");

const catchError = async (ctx, next) => {
  try {
    await next();
  } catch (error) {
    if (error instanceof HttpError) {
      ctx.body = {
        code: error.errorCode,
        msg: error.msg,
        url: `${ctx.method}  ${ctx.path}`
      };
      ctx.status = error.status;
    } else {
      ctx.body = {
        code: 9999,
        msg: "服务器发生错误",
        url: `${ctx.method}  ${ctx.path}`
      };
      ctx.status = 500;
    };
    //生产环境抛出错误
    if (global.config.env === "dev") {
      console.error(error);
    };
  }
}


module.exports = catchError;

本来是想直接throw抛出的,但是抛出后,后端能显示错误,前端又收不到body的内容,所以改为console输出了。

分类: Node 标签: nodekoaerror错误处理

评论

暂无评论数据

暂无评论数据

目录