前端接口请求数据是再熟悉不过了,但是有些时候网络问题,或者其他问题导致的失败请求还是很常见的!有些是真的失败需要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"
})
};
好了说到这里都已经说的差不多了,有不正的地方请指正,不胜感激!!欢乐的时光总是过得特别快,又到时候和大家讲拜拜!!