我转过几个弯 绕过那个小雨楼
拿着蒲扇摆着衣衫渡着紧箍咒
不问天涯不停留 喝过几壶酒
不过年少白头道义放胸口
倘若明天之后 遥看前尘剑封侯
似那天上神仙无所求
朝朝暮暮君如梦醒十分不为何理由
是真是假是惶恐是无休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
又过了几个弯 算尽天量道莫慌
踏这田园闻这芳草香
跌跌撞撞仗剑天涯折煞不枉无笔良
是梦是幻是温柔是家乡
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
路过这风雨花满楼 片刻都不停留
我本这书生进京赶考留下许多愁
你问有没有时候 我叹这天道默悠悠
能否与我一醉方休
谁能与我一醉方休
koa教程2 错误处理
错误捕获
在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
中间件捕获。
错误的处理
错误分两种:
- 已知错误
- 未知错误
已知错误时我们能预先判断到的错误,比如参数校验不通过,未知则是由不知道哪里的代码所产生的错误。
那么处理已知错误,我们可以自己对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本身就是表示服务发生错误。
这样就行了,相对来说比较简单的。
开发环境和正式环境错误处理
目前,我们所有的错误都被全局错误处理捕获到了,但是也正因为如此,导致我们后端没办法具体的看到错误的代码信息,所以我们要针对不同的环境做出错误处理。
一般就两种环境:
- 开发环境:development
- 正式环境: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输出了。
评论(0)