IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 优雅地处理前端中的重试逻辑 -> 正文阅读

[JavaScript知识库]优雅地处理前端中的重试逻辑

一、不那么优雅的逻辑

「重试」是前端开发中一个非常常见的场景,但因为 Promise 的异步回调特性,导致我们在处理重试逻辑的时候很容易就陷入到了「回调地狱」当中,比如这种递归和 Promise 结合写的重试函数:

// 工具函数,用于延迟一段时间
const sleep = (second: number = 1000) => {
  return new Promise((resolve) => {
    setTimeout(resolve, second);
  });
};

// 工具函数,用于递归重试函数
const retry = <T>(
  promise: Promise<T>,
  resolve: (value: unknown) => void,
  reject: (reason?: any) => void,
  retryTimes: number,
  retryMaxTimes: number
) => {
  sleep().then(() => {
    promise
      .then((res: any) => {
        resolve(res);
      })
      .catch((err: any) => {
        if (retryTimes >= retryMaxTimes) {
          reject(err);
          return;
        }
        retry(promise, resolve, reject, retryTimes + 1, retryMaxTimes);
      });
  });
};

// 重试函数
export const retryRequest = <T>(promise: Promise<T>, retryMaxTimes: number) => {
  return new Promise((resolve, reject) => {
    let count = 1;
    promise
      .then((res) => {
        resolve(res);
      })
      .catch((err) => {
        if (count >= retryMaxTimes) {
          reject(err);
          return;
        }
        retry(promise, resolve, reject, count + 1, retryMaxTimes);
      });
  });
};

而使用 asyncawait 的话,情况也好不了太多,我们还是要用递归,而且还要在 await 函数外面包一层「try - catch」也不太直观:

// 工具函数,用于延迟一段时间
const sleep = (second: number = 1000) => {
  return new Promise((resolve) => {
    setTimeout(resolve, second);
  });
};

// 工具函数,用于递归重试函数
const retry = async <T>(
  promise: Promise<T>,
  retryTimes: number,
  retryMaxTimes: number
) => {
  await sleep();

  try {
    let result = await promise;
    return result;
  } catch (err) {
    if (retryTimes >= retryMaxTimes) {
      throw err;
    }
    retry(promise, retryTimes + 1, retryMaxTimes);
  }
};

// 重试函数
export const retryRequest = async <T>(
  promise: Promise<T>,
  retryMaxTimes: number
) => {
  let count = 1;
  try {
    let result = await promise;
    return result;
  } catch (err) {
    if (count >= retryMaxTimes) {
      throw err;
    }
    retry(promise, count + 1, retryMaxTimes);
  }
};

二、优雅的逻辑

那么怎么样优雅的写重试逻辑呢?结合第一部分中那些不那么优雅的逻辑,我们可以知道,如果想要提升可读性,我们得做如下三件事:

1、消灭回调
2、消灭 try - catch 这种异常处理逻辑
3、消灭递归逻辑

2-1、回调以及异常处理

为了消灭回调函数,我们还是选择了 asyncawait 语法,而对于异常处理,我们则封装了一个函数,把异常处理逻辑拉平,就像下面这样:

const awaitErrorWrap = async <T, U = any>(
  promise: Promise<T>
): Promise<[U | null, T | null]> => {
  try {
    const data = await promise;
    return [null, data];
  } catch (err: any) {
    return [err, null];
  }
};

这样我们就可以像 go 语言一样,不用包裹 try - catch,直接拿到异常了,就像下面这样:

const [err, data] = await awaitErrorWrap(promise);

2-2、将递归函数改成循环

我们将原有的递归函数改成一个 for 循环,这样逻辑就更清晰了:

const retryRequest = async <T>(promise: Promise<T>, retryTimes: number = 3) => {
  let output: [any, T | null] = [null, null];

  for (let a = 0; a < retryTimes; a++) {
    output = await awaitErrorWrap(promise);

    if (output[1]) {
      break;
    }
  }

  return output;
};

2-3、优雅的重试逻辑

那么我们将上面的代码组合起来,就得出了下面这个很简洁明了且优雅的重试逻辑了:

// 工具函数,用于延迟一段时间
const sleep = (time: number) => {
  return new Promise((resolve) => {
    setTimeout(resolve, time);
  });
};

// 工具函数,用于包裹 try - catch 逻辑
const awaitErrorWrap = async <T, U = any>(
  promise: Promise<T>
): Promise<[U | null, T | null]> => {
  try {
    const data = await promise;
    return [null, data];
  } catch (err: any) {
    return [err, null];
  }
};

// 重试函数
export const retryRequest = async <T>(
  promise: Promise<T>,
  retryTimes: number = 3,
  retryInterval: number = 500
) => {
  let output: [any, T | null] = [null, null];

  for (let a = 0; a < retryTimes; a++) {
    output = await awaitErrorWrap(promise);

    if (output[1]) {
      break;
    }

    console.log(`retry ${a + 1} times, error: ${output[0]}`);
    await sleep(retryInterval);
  }

  return output;
};

我们可以这样使用它:

import { retryRequest } from "xxxx";
import axios from "axios";

const request = (url: string) => {
  return axios.get(url);
};

const [err, data] = await retryRequest(request("https://request_url"), 3, 500);

2-4、更进一步?写个装饰器吧

当然写到这里肯定有朋友会说:“每次在 Promise 外面包一层这个函数也不够优雅!”

没问题,我们可以更进一步,写一个 typescript 的装饰器:

export const retryDecorator = (
  retryTimes: number = 3,
  retryInterval: number = 500
): MethodDecorator => {
  return (_: any, __: string | symbol, descriptor: any) => {
    const fn = descriptor.value;
    descriptor.value = async function (...args: any[]) {
      // 这里的 retryRequest 就是刚才的重试函数
      return retryRequest(fn.apply(this, args), retryTimes, retryInterval);
    };
  };
};

这样我们就可以直接这样使用这个装饰器了:

import { retryRequest } from 'xxxx'
import axios from 'axios'

class RequestClass {
	@retryDecorator(3, 500)
	getUrl(url) => {
		return axios.get(url)
	}
}

const [err, data] = await RequestClass.getUrl('https://request_url')
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-30 18:14:48  更:2022-03-30 18:15:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:24:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码