之前在做 code review 时候发现有共事应用 try catch 包装了一堆异步代码,于是集体就感觉很奇怪,难道不应该只 catch 可能出问题的代码吗?共事通知我说 try catch 太细的话会呈现内外作用域不统一,须要提前申明变量。
let res: Data[] = [];try { res = await fetchData();} catch (err) { // 错误操作或者终止 // return}// 继续执行失常逻辑
确实,一方面开发者不应该大范畴包裹非异样代码,另一方面提前申明变量会让代码不连贯同时也会打断思路。其中一个形式是间接应用原生 Promie 而不是 async。
fetchData().then((res) => {}).catch((err) => {});
这样对于单个异步申请当然没有任何问题,如果是具备依赖性的异步申请。尽管能够再 Promise 中返回另外的 Promise 申请,然而这样解决 catch 却只能有一个。
fetchData().then((res) => { // 业务解决 return fetchData2(res);}).then((res) => { // 业务解决}).catch((err) => { // 只能做一个通用的错误处理了});
如果须要多个 catch 解决,咱们就须要这样写。
fetchData().then((res) => { // 业务解决 return fetchData2(res);}).catch((err) => { // 错误处理并且返回 null return null;}).then((res) => { if (res === null) { return; } // 业务解决}).catch((err) => { // 错误处理});
这时候开发者也要思考 fetchData2 会不会返回 null 的问题。于是集体开始找一些办法来帮忙咱们解决这个问题。
await-to-js
await-to-js 是一个辅助开发者解决异步谬误的库。咱们先来看看该库是如何解决咱们问题的。
import to from "await-to-js";const [fetch1Err, fetch1Result] = await to(fetchData());if (fetch1Err) { // 错误操作或者终止 // return}const [fetch2Err, fetch1Result] = await to(fetchData2(fetch1Result));if (fetch2Err) { // 错误操作或者终止 // return}
源码非常简单。
export function to( promise, errorExt,) { return promise .then((data) => [null, data]) .catch((err) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; });}
应用 try-run-js
看到 await-to-js 将谬误作为失常流程的一部分,于是集体想到是不是能通过 try catch 解决一些异步代码问题呢?
我立即想到了须要获取 DOM 节点的需要。现有框架都应用了数据驱动的思路,然而 DOM 具体什么时候渲染是未知的,于是集体想到之前代码,Vue 须要获取 ref 并进行回调解决。
function resolveRef(refName, callback, time: number = 1) { // 超过 10 次跳出递归 if (time > 10) throw new Error(`cannot find ref: ${refName}`); // const self = this; // 获取 ref 节点 const ref = this.$refs[refName]; if (ref) { callback(ref); } else { // 没有节点就下一次 this.$nextTick(() => { resolveRef.call(self, refName, callback, time + 1); }); }}
当然了,上述代码确实能够解决此类的问题,在解决此类问题时候咱们能够替换 ref 和 nextTick 的代码。于是 await-to-js 的逻辑下,集体开发了 try-run-js 库。咱们先看一下该库如何应用。
import tryRun from "try-run-js";tryRun(() => { // 间接尝试应用失常逻辑代码 // 千万不要增加 ?. // 代码不会出错而不会重试 this.$refs.navTree.setCurrentKey("xxx");}, { // 重试次数 retryTime: 10, // 下次操作前须要的延迟时间 timeout: () => { new Promise((resolve) => { this.$nextTick(resolve); }); },});
咱们也能够获取谬误数据和后果。
import tryRun from "try-run-js";const getDomStyle = async () => { // 获取一步的 const { error: domErr, result: domStyle } = await tryRun(() => { // 返回 dom 节点款式,不必管是否存在 ppt // 千万不要增加 ?. // 代码不会出错而返回 undefined return document.getElementById("domId").style; }, { // 重试次数 retryTime: 3, // 返回数字的话,函数会应用 setTimeout // 参数为以后重试的次数,第一次重试 100 ms,第二次 200 timeout: (time) => time * 100, // 还能够间接返回数字,不传递默认为 333 // timeout: 333 }); if (domErr) { return {}; } return domStyle;};
当然了,该库也是反对返回元组以及 await-to-js 的 Promise 错误处理的性能的。
import { tryRunForTuple } from "try-run-js";const [error, result] = await tryRunForTuple(fetchData());
try-run-js 我的项目演进
try-run-js 外围在于 try catch 的解决,上面是对于 try-run-js 的编写思路。心愿能对大家有一些帮忙
反对 await-to-js
const isObject = (val: any): val is Object => val !== null && (typeof val === "object" || typeof val === "function");const isPromise = <T>(val: any): val is Promise<T> => { // 继承了 Promise // 领有 then 和 catch 函数,对应手写的 Promise return val instanceof Promise || ( isObject(val) && typeof val.then === "function" && typeof val.catch === "function" );};const tryRun = async <T>( // 函数或者 promise promiseOrFun: Promise<T> | Function, // 配置我的项目 options?: TryRunOptions,): Promise<TryRunResultRecord<T>> => { // 以后参数是否为 Promise const runParamIsPromise = isPromise(promiseOrFun); const runParamIsFun = typeof promiseOrFun === "function"; // 既不是函数也不是 Promise 间接返回谬误 if (!runParamIsFun && !runParamIsPromise) { const paramsError = new Error("first params must is a function or promise"); return { error: paramsError } as TryRunResultRecord<T>; } if (runParamIsPromise) { // 间接应用 await-to-js 代码 return runPromise(promiseOrFun as Promise<T>); }};
执行谬误重试
接下来咱们开始利用 try catch 捕捉函数的谬误并且重试。
// 默认 timeoutconst DEFAULT_TIMEOUT: number = 333// 异步期待const sleep = (timeOut: number) => { return new Promise<void>(resolve => { setTimeout(() => { resolve() }, timeOut) })}const tryRun = async <T>( promiseOrFun: Promise<T> | Function, options?: TryRunOptions,): Promise<TryRunResultRecord<T>> => { const { retryTime = 0, timeout = DEFAULT_TIMEOUT } = { ...DEFAULT_OPTIONS, ...options, }; // 以后第几次重试 let currentTime: number = 0; // 是否胜利 let isSuccess: boolean = false; let result; let error: Error; while (currentTime <= retryTime && !isSuccess) { try { result = await promiseOrFun(); // 执行完并获取后果后认为以后是胜利的 isSuccess = true; } catch (err) { error = err as Error; // 尝试次数加一 currentTime++; // 留神这里,笔者在这里犯了一些谬误 // 如果没有解决好就会执行不须要解决的 await // 1.如果以后不须要从新申请(重试次数为 0),间接跳过 // 2.最初一次也失败了(重试完了)也是要跳过的 if (retryTime > 0 && currentTime <= retryTime) { // 获取工夫 let finalTimeout: number | Promise<any> = typeof timeout === "number" ? timeout : DEFAULT_TIMEOUT; // 如果是函数执行函数 if (typeof timeout === "function") { finalTimeout = timeout(currentTime); } // 以后返回 Promise 间接期待 if (isPromise(finalTimeout)) { await finalTimeout; } else { // 如果最终后果不是 number,改为默认数据 if (typeof finalTimeout !== "number") { finalTimeout = DEFAULT_TIMEOUT; } // 这里我尝试应用了 NaN、 -Infinity、Infinity // 发现 setTimeout 都进行了解决,上面是浏览器的解决形式 // If timeout is an Infinity value, a Not-a-Number (NaN) value, or negative, let timeout be zero. // 正数,无穷大以及 NaN 都会变成 0 await sleep(finalTimeout); } } } } // 胜利或者失败的返回 if (isSuccess) { return { result, error: null }; } return { error: error!, result: undefined };};
这样,咱们根本实现了 try-run-js.
增加 tryRunForTuple 函数
这个就很简略了,间接间接 tryRun 并革新其后果:
const tryRunForTuple = <T>( promiseOrFun: Promise<T> | Function, options?: TryRunOptions): Promise<TryRunResultTuple<T>> => { return tryRun<T>(promiseOrFun, options).then(res => { const { result, error } = res if (error) { return [error, undefined] as [any, undefined] } return [null, result] as [null, T] })}
代码都在 try-run-js 中,大家还会在什么状况下应用 try-run-js 呢?同时也欢送各位提交 issue 以及 pr。
激励一下
如果你感觉这篇文章不错,心愿能够给与我一些激励,在我的 github 博客下帮忙 star 一下。
博客地址
参考资料
await-to-js
try-run-js