最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 我的前端请求我做主

    正文概述 掘金(虚竹子)   2021-03-27   500

    上述例子在开发中经常遇到,解决倒是很简单,但如何保证页面的完整性是一个问题。

    ?

    我们需要请求三个接口,分别是接口a、接口b和接口c。请求过程中由于服务不稳定导致a成功了但b和c失败了。这种情况我们要么直接报错误信息,有点不友好;要么只使用a返回的数据,一般这种情况前端都会有默认值,页面自然而然展示出了默认信息,用户就会用错误的数据做出错误的决策,这是很严重的错误。为了解决上述问题,本文实现了一个容错机制,去尝试解决这个问题。

    实现

    重试方案

    首先重试机制意味着我们需要多次请求失败接口,先解决这个问题,大概两张方案:

    • socket
    • 轮询

    但是我们没有后端支持,socket直接干掉。那么只能通过轮询去尝试。先贴出轮询代码

    export interface IParams {
      maxCount?: number; // 最大轮询次数
      intervalTime?: number; // 每次轮询增加时间长度
      maxInterval?: number; // 最大轮询时间长度
    }
    export interface IProcessPayload<T> {
      data: T;
      count: number;
      resolve?: (data: T) => void;
      reject?: (err: any) => void;
    }
    /**
     * 
     * error 失败
     * process 继续轮询
     * finish 结束轮询
     */
    export type IProgressType = 'error' | 'process' | 'finish';
    const defaultConfig = {
      maxCount: 120,
      intervalTime: 1000,
      maxInterval: 1600,
    };
    export class PollingFun {
      timeoutTimer: any;
      cancelWhile: any;
      constructor(private config: IParams = { maxCount: 120, intervalTime: 1000, maxInterval: 1600 }) {
        this.config = { ...defaultConfig, ...config };
      }
      cancel() {
        if (this.cancelWhile) {
          this.cancelWhile();
          this.cancelWhile = null;
        }
        if (this.timeoutTimer) {
          clearTimeout(this.timeoutTimer);
        }
      }
      pollingSingleTask = async <T>(onProgress: (data: IProcessPayload<T>) => IProgressType, ajaxFun: () => Promise<T>) => {
        const { maxCount, intervalTime, maxInterval } = this.config;
        let pollingCount = 0;
        let stopPolling = false;
        this.cancel();
        this.cancelWhile = () => (stopPolling = true);
        while (!stopPolling && pollingCount < maxCount) {
          // 刚开始密集,后续间隔加长,最长1s。
          let realIntervalTime = Math.floor(pollingCount / 10) * 200 + intervalTime; // eslint-disable-line
          realIntervalTime = Math.min(realIntervalTime, maxInterval);
          try {
            const resData = await ajaxFun();
            if (stopPolling) {
              return Promise.reject('cancel');
            }
            const progressRes = onProgress({ data: resData, count: pollingCount });
            switch (progressRes) {
              case 'finish':
                stopPolling = true;
                return Promise.resolve(resData);
              case 'error':
                stopPolling = true;
                return Promise.reject(resData);
              default:
                await new Promise(resolve => {
                  this.timeoutTimer = setTimeout(resolve, realIntervalTime);
                });
                break;
            }
          } catch (error) {
            stopPolling = true;
            return Promise.reject(error);
          }
          pollingCount += 1;
        }
        if (pollingCount >= maxCount) {
          return Promise.reject('overMaxCount');
        }
      };
    }
    

    可以看到我们实现了一个轮询类,使用方式也很简单,只需要每次new一个实例,然后调用对应的方法即可。轮询方法需要两个参数,一个是轮询处理函数,其接受一个参数,会携带本次轮询的数据,我们只需要对数据做判断,然后返回相应的数据处理轮询。

    const pollInstance = new PollingFun();
    pollInstance.pollingSingleTask(process, ajaxFun);
    

    重试机制

    由上述背景我们可以知道,请求成功意味着所有请求都返回了结果,脑袋一转,想到了Promise.all,瞬间解决了一半的问题。我们只需要把每个请求函数包裹成轮询的方式,然后等着拿值就行,上手开干!

    type AjaxFun<T> = [() => Promise<T>, (data: IProcessPayload<T>) => IProgressType, IParams];
    const createPromise = <T>(ajaxFunArr: AjaxFun<T>[]) => {
      return ajaxFunArr.map(item => {
        const [ajaxFn, onProcess, options] = item;
        const pollInstance = new PollingFun(options);
        return new Promise((resolve, reject) => {
          pollInstance.cancel();
          pollInstance
            .pollingSingleTask(payload => onProcess({ ...payload, resolve, reject }), ajaxFn)
            .catch(err => reject(err));
        });
      });
    };
    export const ajaxCatch = async <T>(ajaxFunArr: AjaxFun<T>[]) => {
      const wrapAjaxFunArr = await createPromise(ajaxFunArr);
      return Promise.all([...wrapAjaxFunArr])
        .then(res => ({
          status: true,
          data: res,
        }))
        .catch(err => ({
          status: false,
          data: err,
        }));
    };
    

    可以看到,我们封装了一个ajax处理函数,这个函数需要一个数组类型参数,每个数组子值需要提供有三个值,分别是当前请求函数、控制轮询状态的函数以及轮询初始化的值。看着还不错 试试效果

    const wake = async val => {
      console.log(val);
      return await val;
    };
    const onProcess = pay => {
      const { data, resolve, count } = pay;
      if (data === 'q2' && count === 3) {
        resolve(data);
        return 'finish';
      }
      if (data === 'q2') {
        return 'process';
      }
      return 'finish';
    };
    export const getData = async () => {
      const res = await ajaxCatch([
        [() => wake('q1'), onProcess, { maxCount: 5 }],
        [() => wake('q2'), onProcess, { maxCount: 5 }],
        [() => wake('q3'), onProcess, { maxCount: 5 }],
      ]);
      console.log(res, 'cdc');
    };
    

    完美达到我们需要的效果!

    总结

    上述实现可以多次重试失败接口,并统一返回结果,用户也不会看到页面闪烁等问题,同时还可以当做轮询函数来使用,可谓一举两得!所以嘛,遇到问题不要慌,慢慢分析一步步解决!


    起源地下载网 » 我的前端请求我做主

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元