JS异步错误捕获的一些事小结(2)

const fetchFailure = () => new Promise((resolve, reject) => { setTimeout(() => {// 模拟请求 if(1) reject('fetch failure...'); }) }) async function main () { try { const res = await fetchFailure(); console.log(res, 'res'); } catch(e) { console.log(e, 'e.message'); } } main();

async 函数会被编译成好几段,根据 await 关键字,以及 catch 等,比如 main 函数就是拆成三段。

1.fetchFailure 2. console.log(res) 3. catch

通过 step 来控制迭代的进度,比如 "next",就是往下走一次,从 1->2,异步是通过 Promise.then() 控制的,你可以理解为就是一个 Promise 链,感兴趣的可以去研究一下。 关键是生成器也有一个 "throw" 的状态,当 Promise 的状态 reject 后,会向上冒泡,直到 step('throw') 执行,然后 catch 里的代码 console.log(e, 'e.message'); 执行。

明显感觉 async/await 的错误处理更优雅一些,当然也是内部配合使用了 Promise。

更进一步

async 函数处理异步流程是利器,但是它也不会自动去 catch 错误,需要我们自己写 try catch,如果每个函数都写一个,也挺麻烦的,比较业务中异步函数会很多。

首先想到的是把 try catch,以及 catch 后的逻辑抽取出来。

const handle = async (fn: any) => { try { return await fn(); } catch(e) { // do sth console.log(e, 'e.messagee'); } } async function main () { const res = await handle(fetchFailure); console.log(res, 'res'); }

写一个高阶函数包裹 fetchFailure,高阶函数复用逻辑,比如此处的 try catch,然后执行传入的参数-函数 即可。

然后,加上回调函数的参数传递,以及返回值遵守 first-error,向 node/go 的语法看齐。如下:

const handleTryCatch = (fn: (...args: any[]) => Promise<{}>) => async (...args: any[]) => { try { return [null, await fn(...args)]; } catch(e) { console.log(e, 'e.messagee'); return [e]; } } async function main () { const [err, res] = await handleTryCatch(fetchFailure)(''); if(err) { console.log(err, 'err'); return; } console.log(res, 'res'); }

但是还有几个问题,一个是 catch 后的逻辑,这块还不支持自定义,再就是返回值总要判断一下,是否有 error,也可以抽象一下。

所以我们可以在高阶函数的 catch 处做一下文章,比如加入一些错误处理的回调函数支持不同的逻辑,然后一个项目中错误处理可以简单分几类,做不同的处理,就可以尽可能的复用代码了。

// 1. 三阶函数。第一次传入错误处理的 handle,第二次是传入要修饰的 async 函数,最后返回一个新的 function。 const handleTryCatch = (handle: (e: Error) => void = errorHandle) => (fn: (...args: any[]) => Promise<{}>) => async(...args: any[]) => { try { return [null, await fn(...args)]; } catch(e) { return [handle(e)]; } } // 2. 定义各种各样的错误类型 // 我们可以把错误信息格式化,成为代码里可以处理的样式,比如包含错误码和错误信息 class DbError extends Error { public errmsg: string; public errno: number; constructor(msg: string, code: number) { super(msg); this.errmsg = msg || 'db_error_msg'; this.errno = code || 20010; } } class ValidatedError extends Error { public errmsg: string; public errno: number; constructor(msg: string, code: number) { super(msg); this.errmsg = msg || 'validated_error_msg'; this.errno = code || 20010; } } // 3. 错误处理的逻辑,这可能只是其中一类。通常错误处理都是按功能需求来划分 // 比如请求失败(200 但是返回值有错误信息),比如 node 中写 db 失败等。 const errorHandle = (e: Error) => { // do something if(e instanceof ValidatedError || e instanceof DbError) { // do sth return e; } return { code: 101, errmsg: 'unKnown' }; } const usualHandleTryCatch = handleTryCatch(errorHandle); // 以上的代码都是多个模块复用的,那实际的业务代码可能只需要这样。 async function main () { const [error, res] = await usualHandleTryCatch(fetchFail)(false); if(error) { // 因为 catch 已经做了拦截,甚至可以加入一些通用逻辑,这里甚至不用判断 if error console.log(error, 'error'); return; } console.log(res, 'res'); }

解决了一些错误逻辑的复用问题之后,即封装成不同的错误处理器即可。但是这些处理器在使用的时候,因为都是高阶函数,可以使用 es6 的装饰器写法。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/6eea9cedb6c69fbd6e115d698db24b57.html