不过装饰器只能用于类和类的方法,所以如果是函数的形式,就不能使用了。不过在日常开发中,比如 React 的组件,或者 Mobx 的 store,都是以 class 的形式存在的,所以使用场景挺多的。
比如改成类装饰器:
const asyncErrorWrapper = (errorHandler: (e: Error) => void = errorHandle) => (target: Function) => { const props = Object.getOwnPropertyNames(target.prototype); props.forEach((prop) => { var value = target.prototype[prop]; if(Object.prototype.toString.call(value) === '[object AsyncFunction]'){ target.prototype[prop] = async (...args: any[]) => { try{ return await value.apply(this,args); }catch(err){ return errorHandler(err); } } } }); } @asyncErrorWrapper(errorHandle) class Store { async getList (){ return Promise.reject('类装饰:失败了'); } } const store = new Store(); async function main() { const o = await store.getList(); } main();
这种 class 装饰器的写法是看到黄子毅 这么写过,感谢灵感。
koa 的错误处理
如果对 koa 不熟悉,可以选择跳过不看。
koa 中当然也可以用上面 async 的做法,不过通常我们用 koa 写 server 的时候,都是处理请求,一次 http 事务会掉起响应的中间件,所以 koa 的错误处理很好的利用了中间件的特性。
比如我的做法是,第一个中间件为捕获 error,因为洋葱模型的缘故,第一个中间件最后仍会执行,而当某个中间件抛出错误后,我期待能在此捕获并处理。
// 第一个中间件 const errorCatch = async(ctx, next) => { try { await next(); } catch(e) { // 在此捕获 error 路由,throw 出的 Error console.log(e, e.message, 'error'); ctx.body = 'error'; } } app.use(errorCatch); // logger app.use(async (ctx, next) => { console.log(ctx.req.body, 'body'); await next(); }) // router 的某个中间件 router.get('/error', async (ctx, next) => { if(1) { throw new Error('错误测试') } await next(); })
为什么在第一个中间件写上 try catch,就可以捕获前面中间件 throw 出的错误呢。首先我们前面 async/await 的地方解释过,async 中await handle(),handle 函数内部的 throw new Error 或者 Promise.reject() 是可以被 async 的 catch 捕获的。所以只需要 next 函数能够拿到错误,并抛出就可以了,那看看 next 函数。
// compose 是传入中间件的数组,最终形成中间件链的,next 控制游标。 compose(middlewares) { return (context) => { let index = 0; // 为了每个中间件都可以是异步调用,即 `await next()` 这种写法,每个 next 都要返回一个 promise 对象 function next(index) { const func = middlewares[index]; try { // 在此处写 try catch,因为是写到 Promise 构造体中的,所以抛出的错误能被 catch return new Promise((resolve, reject) => { if (index >= middlewares.length) return reject('next is inexistence'); resolve(func(context, () => next(index + 1))); }); } catch(err) { // 捕获到错误,返回错误 return Promise.reject(err); } } return next(index); } }
next 函数根据 index,取出当前的中间件执行。中间件函数如果是 async 函数,同样的转化为 generator 执行,内部的异步代码顺序由它自己控制,而我们知道 async 函数的错误是可以通过 try catch 捕获的,所以在 next 函数中加上 try catch 捕获中间件函数的错误,再 return 抛出去即可。所以我们才可以在第一个中间件捕获。详细代码可以看下简版 koa
然后 koa 还提供了 ctx.throw 和全局的 app.on 来捕获错误。
如果你没有写错误处理的中间件,那可以使用 ctx.throw 返回前端,不至于让代码错误。
但是 throw new Error 也是有优势的,因为某个中间件的代码逻辑中,一旦出现我们不想让后面的中间件执行,直接给前端返回,直接抛出错误即可,让通用的中间件处理,反正都是错误信息。
// 定义不同的错误类型,在此可以捕获,并处理。 const errorCatch = async(ctx, next) => { try { await next(); } catch (err) { const { errmsg, errno, status = 500, redirect } = err; if (err instanceof ValidatedError || err instanceof DbError || err instanceof AuthError || err instanceof RequestError) { ctx.status = 200; ctx.body = { errmsg, errno, }; return; } ctx.status = status; if (status === 302 && redirect) { console.log(redirect); ctx.redirect(redirect); } if (status === 500) { ctx.body = { errmsg: err.message, errno: 90001, }; ctx.app.emit('error', err, ctx); } } } app.use(errorCatch); // logger app.use(async (ctx, next) => { console.log(ctx.req.body, 'body'); await next(); }) // 通过 ctx.throw app.use(async (ctx, next) => { //will NOT log the error and will return `Error Message` as the response body with status 400 ctx.throw(400,'Error Message'); }); // router 的某个中间件 router.get('/error', async (ctx, next) => { if(1) { throw new Error('错误测试') } await next(); }) // 最后的兜底 app.on('error', (err, ctx) => { /* centralized error handling: * console.log error * write error to log file * save error and request information to database if ctx.request match condition * ... */ });
最后
本文的代码都存放于此