需求的诞生:
先简单介绍一下业务场景,我们的项目是一个微博舆情分析系统,可以根据用户设置的关键字监测相关微博舆情,并进行实时推送。监测范围涵盖境内和境外微博平台(境内:新浪、腾讯,境外:twitter)。
因为访问境外的微博需要经过代理转发,导致经常有图片加载不出来,严重影响用户观感。此时就需要一个错误重试的处理,不要让界面上出现这个图片加载错误的图标,如下所示:
思路:
通过外部传进来的代表图片来源的参数,判断是否是境外图片。如果是,则先显示一张默认图片,等图片加载成功后再替换默认图片。如果图片加载错误了,间隔一段时间进行重试,直到加载成功,或失败次数大于设定的最大阈值时停止。
预备知识:
图片加载主要有三种状态:成功、失败、加载中(pendding),这个可以在控制台的NetWork里面看到。
开始撸:
先写一个函数,里面定义图片加载失败后的最大重试次数
maxCount
,初始化错误重试次数,并对图片url进行一个非空验证
/**
* @param imageUrl {string} 图片URL
* @param callBack {function} 图片加载成功后的回调(参数:{imageUrl})
*/
function loadImage(imageUrl, callBack) {
if (!imageUrl) {
return callBack({
imageUrl: ''
});
}
const maxCount = 5; // 最大重试次数
let count = 0; // 重试次数
// 验证图片是否加载成功
validateImage(imageUrl, callBack, maxCount, count);
}
复制代码
接下来就是核心代码
validateImage
函数的实现了, 首先定义好函数内部的变量
function validateImage(imageUrl, callBack, maxCount, count) {
var img = new Image(), // 创建一个image对象
timeId,
timer,
finished, // 图片加载成功标志位
time = 40, // 观察图片状态的定时器执行间隔
timeCount = 0, // 观察图片状态的定时器执行次数
totalTime = 0, // 观察图片状态的定时器累计执行时间
errTimeout = 1000; // 失败重试的延时
复制代码
然后给
img.src
赋值,此时图片就开始加载了
img.src = imageUrl;
count++;
复制代码
用
img.complete
判断图片是否加载完毕,为true,则表示图片已加载成功,直接执行回调函将正确的图片路径返回
if (img.complete) {
callBack({
imageUrl: imageUrl,
});
}
复制代码
如果
img.complete
为false,则需要分情况处理。
情况一:图片加载中(pendding状态),我们需要定时去看图片状态,直到图片加载成功或者失败,用
onload
事件或onerror
事件去捕获。
然而这个地方有个大坑,什么坑呢?
图片有可能一直处于pendding状态,此时是不会有任何返回值的,所以没有办法用onload
或onerr
事件捕获,定时器一直重复执行,内存狂飙!
else {
if(totalTime < 60000){
timeId = setTimeout(function () {
timer();
timeCount++;
// 每次执行间隔是上次执行间隔的两倍
time = 40*Math.pow(2,timeCount);
totalTime += time;
}, time);
}else{
clearTimeout(timeId);
}
}
};
// 执行观察定时器
timeId = setTimeout(function () {
timer();
totalTime += time;
}, time);
复制代码
此时如果图片加载成功或失败则会被onload捕获或onerr捕获。图片加载成功进入到
onload
里面,执行回调并将正确的图片路径返回;图片加载失败,进入到onerror
里面,并进行重试,直到成功或者重试次数大于maxCount
结束。
img.onload = function () {
clearTimeout(timeId);
if (!finished) {
return callBack({
imageUrl: imageUrl
});
}
};
img.onerror = function () {
if (count < maxCount) {
clearTimeout(timeId);
// 错误重试间隔每次以2s递增
errTimeout += (count-1) * 2000;
// 递归
timeId = setTimeout(function () {
validateImage(imageUrl, callBack, maxCount, count);
}, errTimeout);
} else {
clearTimeout(timeId);
if (!finished) {
callBack({
imageUrl: ''
});
}
}
};
}
复制代码
ok,大功告成!
写的不好,仅供参考。谢谢大家
源码:
/**
* 验证图片是否加载成功
* @param imageUrl
* @param callBack
* @param maxCount {string} 最大轮询次数
* @param count {string} 重新加载次数
*/
function validateImage(imageUrl, callBack, maxCount, count) {
let img = new Image(), timeId, timer, finished, time = 40, timeCount = 0, totalTime = 0, errTimeout = 1000;
img.src = imageUrl;
count++;
if (img.complete) {
callBack({
imageUrl: imageUrl,
});
} else {
timer = function () {
if (img.width > 0 || img.height > 0) {
finished = true;
callBack({
imageUrl: imageUrl
});
} else {
if(totalTime < 60000){
timeId = setTimeout(function () {
timer();
timeCount++;
time = 40*Math.pow(2,timeCount);
totalTime += time;
}, time);
}else{
clearTimeout(timeId);
}
}
};
timeId = setTimeout(function () {
timer();
totalTime += time;
}, time);
img.onload = function () {
clearTimeout(timeId);
if (!finished) {
return callBack({
imageUrl: imageUrl
});
}
};
img.onerror = function () {
if (count < maxCount) {
clearTimeout(timeId);
errTimeout += (count-1) * 2000;
timeId = setTimeout(function () {
validateImage(imageUrl, callBack, maxCount, count);
}, errTimeout);
} else {
clearTimeout(timeId);
if (!finished) {
callBack({
imageUrl: ''
});
}
}
};
}
}
/**
* @param imageUrl {string} 图片URL
* @param callBack {function} 图片加载成功后的回调(参数:{imageUrl})
* @returns {*}
*/
function loadImage(imageUrl, callBack) {
if (!imageUrl) {
return callBack({
imageUrl: ''
});
}
const maxCount = 5;
let count = 0;
validateImage(imageUrl, callBack, maxCount, count);
}
export default loadImage;
复制代码