前端關於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"
    })
};

好了說到這裏都已經說的差不多了,有不正的地方請指正,不勝感激!!歡樂的時光總是過得特別快,又到時候和大家講拜拜!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章