关于javascript:从-awaittojs-到-tryrunjs

41次阅读

共计 5241 个字符,预计需要花费 14 分钟才能阅读完成。

之前在做 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 捕捉函数的谬误并且重试。

// 默认 timeout
const 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

正文完
 0