木灵鱼儿
阅读:1879
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输出了。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
centos 安装最新版 node.js
[hide]先用yum 安装一次node先升级yum update 提示下载程序,回复y安装nodeyum install nodejsyum安装的node一般都不是最新的,所以我们还需要升级node的版本npm i n -g全局安装一个n插件n lts表示安装最新的稳定版!一些常用命令n ls查看已安装的node版本n rm 16.0.3删除16.0.3版本更多功能阅读插件文档:n等待安装完毕后,此时其实版本已经安装好了,但是输入node -v还是低版本,是因为centos的用户缓存问题。我们退出登录再登录就会刷新缓存了,此时的node -v输出的就是最新版本exit 退...
node npm yarn 如何同时运行多个指令
node的bash命令其实是有对应的指令字符的,但是,这个指令只能再linux,mac上才有生效,再windows是无效的,所以了解一下就行。命令说明&&顺序执行多条命令,当碰到执行出错的命令后将不执行后面的命令&并行执行多条命令||顺序执行多条命令,当碰到执行正确的命令后将不执行后面的命令|管道符windows上进行多命令"scripts": { "dev": "cross-env NODE_ENV=development webpack --env development --progress -...
koa教程2 HttpBearerAuth 传递token
前端发送token到后端,一般有三种方式:链接中携带token,query参数post这种请求body中携带token头信息携带token但是事实上http已经为我们提供了传token的方式,是一个相对来说比较安全的方式,就是HttpBearerAuth 其实他也是一个头信息来着,只是相对来说多了一点操作,在apipost软件中,我们可以直接可视化设置如果是axios,则是需要添加一个头信息:axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authoriz...

koa教程2 微信openid登录
微信的openid,前端登录时,会发送一个code码到后端,后端接收到这个code,然后配合appid、appsecret参数,像wx发送一个get请求。wx的请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code具体查看文档:微信官方文档-服务端可以看到这段get请求有四个参数:属性类型默认值必填说明appidstring 是小程序 appIdsecretstring 是...
koa教程2 权限分级控制
在之前的代码中,我们使用了一个scope属性来表示用户的权限,但是scope是一个数字,他的阅读性是非常差的,当其他人开发的时候,可能并不知道你这个数字是什么意思?所以,我们需要将它转义成文本。为了方便判断,我们在enum.js中增加一个用户等级的对象enum.js://通用判断是否存在该类型 function isThisType(val) { let flag = false; Object.keys(this).find(key => { if (this[key] === val) { return flag = true; } })...
koa教程2 token鉴权
之前我们在token中传入我们的自定义参数,现在我们要做一个token鉴权,很简单,如果他的token合法,就可以访问这个路由,如果不合法,就打回。这个也不能说是真正意义上的鉴权,因为他还没有判断scope值,所以只能说是简单判断。做这个判断我们就可以通过中间件的形式,于是我们在middlewares目录下创建一个auth.js文件auth.js:const parseBearerToken = require("@utils/paresBearerToken"); const jwt = require("jsonwebtoken"); cons...
koa教程2 用户登录
登录类型用户注册之后,自然是用户登录,登录的时候我们需要考虑到登录的方式,往往在正式的项目里面,登录的方式可能会有很多种:账号密码登录手机号登录小程序一键登录那么我们登录的时候就要进行判断,设置一个type的属性,用于在登录时判断用户是什么类型的登录,这个由前端那边设定参数。type:1为账号密码登录,2为手机号登录,3为小程序一键登录作为判断,我们可以写一个查询表(枚举),在项目根目录创建一个lib目录,在里面创建一个enum.js文件。enum.js://通用判断是否存在该类型 function isThisType(val) { let flag = false; Obje...
node 自定义路径别名
用过vue cli的话,对路径中的@应该不陌生,他表示根目录,但是在node的环境里面,我们编写模块并引入时,并没有这种方便的写法。在node环境中,我们可以通过process.cwd()来表示根目录,但是这样的话,每次使用绝对路径引入,路径总是需要写一大堆拼接。const db = require(`${process.cwd()}/config/config`).database;这样略显麻烦,于是我们可以在全局写一个引入插件的方法:global.myRequire = function (path) { return require(`${process.cwd()}/${pa...
koa教程2 密码加密
默认情况下,我们通过User.create({ email, password, nickname });模型传入的数据,是无加密的,也就是说,在数据库中密码是明文显示的,这样肯定是不行,我们需要使用bcrypt插件进行加密yarn add bcryptjs --dev打开models/user.jsuser.js:const bcrypt = require("bcryptjs"); const { sequelize } = require(`${process.cwd()}/core/db`); const { Sequelize, Model, DataTy...

koa教程2 使用sequelize连接数据库,创建用户模型
数据库的连接处理,我们需要使用sequelize插件。这个插件还有对应的数据库驱动,如果是使用mysql,那么驱动是mysql2,具体可以查看中文版的入门教程:入门yarn add sequelize mysql2 --dev安装完成后我们先需要创建数据库连接。连接数据库在根目录/core目录**下创建一个db.js文件用于连接数据库。在连接数据库之前,我们需要一些数据库的参数,如账号,密码,端口,数据库名字,数据库地址,为了方便管理配置,我们在根目录创建一个config目录,里面存放一config.js文件 ,这个之前的教程我们就已经创建好了。config.js:module.expo...