前端接口請求數據是再熟悉不過了,但是有些時候網絡問題,或者其他問題導致的失敗請求還是很常見的!有些是真的失敗需要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"
})
};
好了說到這裏都已經說的差不多了,有不正的地方請指正,不勝感激!!歡樂的時光總是過得特別快,又到時候和大家講拜拜!!