之前在做 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