需求的誕生:
先簡單介紹一下業務場景,我們的專案是一個微博輿情分析系統,可以根據使用者設定的關鍵字監測相關微博輿情,並進行實時推送。監測範圍涵蓋境內和境外微博平臺(境內:新浪、騰訊,境外:twitter)。
因為訪問境外的微博需要經過代理轉發,導致經常有圖片載入不出來,嚴重影響使用者觀感。此時就需要一個錯誤重試的處理,不要讓介面上出現這個圖片載入錯誤的圖示,如下所示:
思路:
通過外部傳進來的代表圖片來源的引數,判斷是否是境外圖片。如果是,則先顯示一張預設圖片,等圖片載入成功後再替換預設圖片。如果圖片載入錯誤了,間隔一段時間進行重試,直到載入成功,或失敗次數大於設定的最大閾值時停止。
預備知識:
圖片載入主要有三種狀態:成功、失敗、載入中(pendding),這個可以在控制檯的NetWork裡面看到。
Image物件有一個屬性complete,可返回瀏覽器是否已完成對影象的載入。complete為true表示圖片已經載入完成,為false則有兩種情況,一是載入失敗,而是正在載入中還沒有結果。開始擼:
先寫一個函式,裡面定義圖片載入失敗後的最大重試次數
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;
複製程式碼