前端关于retry组件的方法和实现

前端接口请求数据是再熟悉不过了,但是有些时候网络问题,或者其他问题导致的失败请求还是很常见的!有些是真的失败需要retry,有些是成功,成功之后再告诉你需要retry, 接下来我来分享下我做项目里面自己手写了一个 retry组件,基本能满足我项目的需求(如果有不正之处,请指正!谢谢!)

1思路

首先需要哪些参数呢?不能一直retry吧,如果要是失败了,几秒钟可以发送几百个请求,服务器都被你搞爆了!那么
第一点就需要考虑请求次数,当我超过这个次数我就不去retry了,直接抛出错误!
第二点就需要考虑retry的间隔时间了,隔多少时间去retry一次
第三点间隔时间控制,可以延长它,也可以缩短它,比如说你设置了间隔时间是1秒,间隔时间控制是2秒,那么第一次间隔时间就是1秒,第二次间隔时间就是12,第二次间隔时间就是12*2,这个可以慢慢时间间隔越来越长,配小数的话也是可以越来越短!(这个参数是我自己添加进去的,其实有些时候前面2个就够了!)

  //retry的参数
  var options = {
      maxRetry: 8, //失败时重试的次数
      interval: 2, //重试之间的间隔时间,单位秒
      intervalMultiplicator: 1 //延长重试之间的间隔
  };
2.实现
1 .我把这个组件封装成一个servce,方便其他的地方也可以调用!
function retry() {};
...........//中间代码省略
exports.retry = retry;

大致框架是这样的!是不是很简单!

2 .初始化参数(如果没有配置,就使用默认的参数)!
/**
 * @param {Object} options retry的参数,是个对象,里面包含maxRetry,interval,intervalMultiplicator
 * @description 初始化参数
 */

retry.prototype.inint = function (options) {
    retry.defaultConfig = {
        maxRetry: 2,
        interval: 3,
        intervalMultiplicator: 1.5
    };
    if (!options) {
        options = retry.defaultConfig;
    } else {
        for (var k in retry.defaultConfig) {
            if (retry.defaultConfig.hasOwnProperty(k) && !(k in options)) {
                options[k] = retry.defaultConfig[k];
            }
        };
    };
    return options;
};
3 retry的几种情况

首先说我这个项目的,有种情况是pdf 下载,返给我的是个文件流,第一次请求是成功,成功会告诉你需要retry。那么之前说pdf 下载时候说过(不清楚的可以点击这里), 要通过 blob对象是解析它才会实现下载功能,问题是第一次它返回给你的不是文件流,而是告诉你需要retry,所以需要通过另外一个reader对象去解析这个blob对象,看它返回给我的是不是需要retry!
文件流我会判断请求头是否要求blob类型

/**
 * @param {Object} response 请求成功的数据
 * @description //判断请求头是否要求blob类型
 */

retry.prototype.isBolb = function (response) {
    return response && response.config.responseType === "blob";
};

创建blob对象

/**
 * @param {Object} response 请求成功的数据
 * @description 创建blob对象
 */

retry.prototype.CreatBolb = function (response) {
    var blob = new Blob([response.data], {
        type: response.data.type
    });
    return blob;
};

项目里面要通过这个字段(‘application/json’)判断是不是要解析blob对象,如果是’application/pdf’就是文件流了

/**
 * @param {Object} response 请求成功的数据
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description 根据这个code或者type类型来决定是否需要rerty
 */

retry.prototype.responseType = function (response, type) {
    return response.data.type === type || response.data.code === type;
};

创建reader对象去解析blob对象

/**
 * @param {Object} blob 二进制对象
 * @description 创建reader对象用来解析blob对象的 二进制转成16进制
 */

retry.prototype.creatReader = function (blob) {
    var reader = new FileReader();
    reader.readAsBinaryString(blob);
    return reader;
};

当解析’application/json’情况,blob对象是否需要被解析,解析完后判断是否需要retry,下面是我项目里面解析完的对象,有个needRetry:true提示!
在这里插入图片描述

/**
 * @param {Object} reader reader对象解析blob对象的
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description //解析blob对象 
 */


retry.prototype.analyzeBolb = function (reader, fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type) {
    var that = this;
    reader.onload = function () {
        //是否需要retry
        if (JSON.parse(reader.result).needRetry) {
            that.needRetry(fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type);
        };

    };
};

retry的实现方法

/**
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description //retry的实现   
 */


retry.prototype.needRetry = function (fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type) {
    var that = this;
    //判断retry次数是否已经用完了
    if (maxRetry !== 0) {
        setTimeout(function () {
            that.toAsync(fn, maxRetry - 1, interval * intervalMultiplicator, intervalMultiplicator, successCallBack, failCallBack, type);
        }, interval * 1000);
    } else {
        //retry完成了还是失败执行失败回调函数
        failCallBack();
        //超出设定次数直接抛出错误
        throw new Error("请求超时");
    };
};

上面是我项目的情况,针对我的项目用的

第二种情况就是后天返成功状态你,里面有个字段告诉你需要retry(少了上面的解析的过程)
第三种情况就是真的请求失败(网络问题,电脑问题等等)

/**
 * 
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description needRetry逻辑处理,最后执行外面的回调函数  
 */



retry.prototype.toAsync = function (fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type) {
    var that = this;
    fn(fn.parameters).then(function (response) {
        //成功状态文件流类型情况
        if (that.isBolb(response)) {
            var blob = that.CreatBolb(response);
            if (that.responseType(response, type)) {
                var reader = that.creatReader(blob);
                that.analyzeBolb(reader, fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type);
            } else {
                //执行成功回调
                successCallBack.call(null, blob);
            };
        } else {
            //成功状态,当有字段提示需要retry的情况
            if (that.responseType(response, type)) {
                that.needRetry(fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type);
            } else {
                //执行成功回调
                return successCallBack.call(null, response);
            };
        };
    }).catch(function (error) {
        //失败状态
        that.needRetry(fn, maxRetry, interval, intervalMultiplicator, successCallBack, failCallBack, type);
    });
};

这个方法把三种情况都写了,最后会返回成功的回调函数,retry失败会返回失败的回调函数!

下面是我封装之后的代码:

'use strict';


function retry($q, $timeout) {
    this.$q = $q;
    this.$timeout = $timeout;
};


/**
 * 
 * @param {Object} fn 请求的promise
 * @param {Object} options retry的参数,是个对象,里面包含maxRetry,interval,intervalMultiplicator
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @description 请求接口重试组件
 */




//第一种调用方式,只有知道返回结果来决定retry
retry.prototype.retry = function (config) {
    config.options = inint(config.options);
    this.resolver(config);
};

//第二种调用方式,报错了时需要retry
retry.prototype.commonRetry = function (config) {
    config.options = inint(config.options);
    return this.commonResolver(config);
}




/**
 * @param {Object} options retry的参数,是个对象,里面包含maxRetry,interval,intervalMultiplicator
 * @description 初始化参数
 */
function inint(options) {
    retry.defaultConfig = {
        maxRetry: 2,
        interval: 3,
        intervalMultiplicator: 1.5
    };
    if (!options) {
        options = retry.defaultConfig;
    } else {
        for (var k in retry.defaultConfig) {
            if (retry.defaultConfig.hasOwnProperty(k) && !(k in options)) {
                options[k] = retry.defaultConfig[k];
            }
        };
    };
    return options;
};



/**
 * @param {Object} response 请求成功的数据
 * @description //判断请求头是否要求blob类型
 */

function isBolb(response) {
    return response && response.config.responseType === "blob";
};




/**
 * @param {Object} response 请求成功的数据
 * @description 创建blob对象
 */

function CreatBolb(response) {
    var blob = new Blob([response.data], {
        type: response.data.type
    });
    return blob;
};





/**
 * @param {Object} response 请求成功的数据
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description 根据这个code或者type类型来决定是否需要rerty
 */

function responseType(response, type) {
    return response.data.type === type || response.data.code === type;
};





/**
 * @param {Object} blob 二进制对象
 * @description 创建reader对象用来解析blob对象的 二进制转成16进制
 */

function creatReader(blob) {
    var reader = new FileReader();
    reader.readAsBinaryString(blob);
    return reader;
};





/**
 * @param {Object} reader reader对象解析blob对象的
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description //解析blob对象 
 */


function analyzeBolb(reader, config, that) {
    reader.onload = function () {
        //是否需要retry
        var result = reader.result;
        var resultToJson = JSON.parse(result);
        if (resultToJson.needRetry && resultToJson.success === true) {
            needRetry(config, that);
        } else {
            config.options.maxRetry = 0;
            needRetry(config, that, result);
        };
    };
};





/**
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description //retry的实现   
 */


function needRetry(config, that, result) {

    //判断retry次数是否已经用完了
    if (config.options.maxRetry !== 0) {
        config.options.maxRetry = config.options.maxRetry - 1;
        config.options.interval = config.options.interval * config.options.intervalMultiplicator;
        that.$timeout(function () {
            that.resolver(config);
        }, config.options.interval * 1000);
    } else {
        //retry完成了还是失败执行失败回调函数
        function safeApply(fn) {
            that.$timeout(function () {
                fn();
            }, 0)
        };
        if (result !== undefined) {
            safeApply(config.failCallBack);
            throw new Error(result);
        } else {
            safeApply(config.failCallBack);
            throw new Error("retry请求超时");
        };
    };
};



/**
 * 
 * @param {Object} fn 请求的promise
 * @param {String} maxRetry  失败时重试的次数
 * @param {String} interval 重试之间的间隔时间,单位秒
 * @param {String} intervalMultiplicator 延长重试之间的间隔
 * @param {Function} successCallBack retry后成功的回调
 * @param {Function} failCallBack retry后失败的回调
 * @param {String} type 请求返回的类型,有时候要通过类型来做 retry 
 * @description needRetry逻辑处理,最后执行外面的回调函数  
 */


retry.prototype.resolver = function (config) {
    var that = this;
    (config.fn).call(config.service, config.parameters).then(function (response) {
        //成功状态文件流类型情况
        if (response.success === true || response.status === 200) {
            if (isBolb(response)) {
                var blob = CreatBolb(response);
                if (responseType(response, config.type)) {
                    var reader = creatReader(blob);
                    analyzeBolb(reader, config, that);
                } else {
                    //执行成功回调
                    config.successCallBack.call(null, blob);
                };
            } else {
                //成功状态,当有字段提示需要retry的情况
                if (responseType(response, config.type)) {
                    needRetry(config, that);
                } else {
                    //执行成功回调
                    return successCallBack.call(null, response);
                };
            };
        };

    }).catch(function (e) {
        config.type = null;
        needRetry(config, that, JSON.stringify(e));
    })
};


// ---------------------------------------------------------------------------------------------------------------------

//普通retry报错的另外一种写法,返回出去是个promise

/**
 * 
 * @param {Function} action 请求接口函数
 * @param {String || Object} parameters 接口参数
 * @param {Function} service 哪个构造函数的方法 
 * @description //retry的实现   
 */



function commonToAsync(config) {
    if (typeof config.fn !== "function") {
        throw new Error("fn must be a function");
    };

    try {
        var retval = config.fn.call(config.service, config.parameters);
        if (retval.hasOwnProperty('$$state')) {
            return retval;
        } else {
            var deferred = this.$q.defer();
            deferred.resolve(retval);
            return deferred.promise;
        }
    } catch (e) {
        deferred.reject(e);
    }
}



/**
 * 
 * @param {Number} interval 请求接口函数
 * @param {Number} maxRetry 请求接口函数
 * @param {Number} interval 请求接口函数
 * @param {String || Object} parameters 接口参数
 * @param {Function} service 哪个构造函数的方法 
 * @description //retry的实现   
 */



function sleep(config, resolver, that) {
    //验证参数
    if (!(config.options.interval === parseFloat(config.options.interval)) || config.options.interval < 0)
        throw new Error("interval must be a positive float");
    // sleep
    that.$timeout(function () {
        return resolver(config.options.maxRetry, config.options.interval * config.options.intervalMultiplicator);
    }, config.options.interval * 1000);
}



/**
 * 
 * @param {Function} action 请求接口函数
 * @param {String || Object} parameters 接口参数
 * @param {Function} service 哪个构造函数的方法 
 * @param {Object} options retry的参数,是个对象,里面包含maxRetry,interval,intervalMultiplicator
 * @description //retry的实现   
 */


retry.prototype.commonResolver = function (config) {
    var that = this;
    function resolver(maxRetry, interval) {
        var result = commonToAsync(config);
        return result.then(function (response) {
                return Promise.resolve(response);
            })
            .catch(function (error) {
                if (maxRetry > 1) {
                    config.options.maxRetry = config.options.maxRetry - 1;
                    config.options.interval = interval * config.options.intervalMultiplicator;
                    return new Promise(function () {
                        return Promise.resolve(sleep(config, resolver, that));
                    });
                };
                console.log('retry超时');
                throw new Error(JSON.stringify(error));
            });
    };
    return resolver(config.options.maxRetry, config.options.interval);
};



exports.retry = ['$q', '$timeout', retry];

外面调用的代码:


//retry的参数
var options = {
    maxRetry: 8, //失败时重试的次数
    interval: 2, //重试之间的间隔时间,单位秒
    intervalMultiplicator: 1 //延长重试之间的间隔
};

var config = {
    fn: requestFn, // 请求接口函数
    parameters: parameters, //请求接口参数
    service: RESTfulService, //哪个service上的方法
    options: options, //retry的配置参数
    successCallBack: successCallBack, //成功回调
    failCallBack: failCallBack, //失败回调
    type: 'application/json' //需要retry的类型
}

//执行retry方法
retry.retry(config);

//成功的回调执行pdf下载
function successCallBack(blob) {
    var pdf = document.createElement('a');
    pdf.href = window.URL.createObjectURL(blob);
    pdf.download = templateConfig.templateName;
    document.body.appendChild(pdf);
    pdf.click();
    pdf.remove();
    window.URL.revokeObjectURL(pdf.href);
    ctrl.loadingStatus = false;
};

//失败的回调,消失loading
function failCallBack() {
    loadingStatus(false);
};

当然你第一个参数promise每次都要去跟新它才行,所以你可以直接封装个请求接口方法,只要你传入url地址和参数直接返回你一个promise,类似这样:

RESTfulService.prototype.templatedownload = function (url,parameters) {
    var that = this;
    var url = url;
    return this.$http({
        method: "GET",
        url: url,
        params: parameters,
        responseType: "blob"
    })
};

好了说到这里都已经说的差不多了,有不正的地方请指正,不胜感激!!欢乐的时光总是过得特别快,又到时候和大家讲拜拜!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值