不需要任何依賴的圖片載入錯誤處理的工具類load-image.js

Leo_縱化發表於2019-02-02

需求的誕生:

先簡單介紹一下業務場景,我們的專案是一個微博輿情分析系統,可以根據使用者設定的關鍵字監測相關微博輿情,並進行實時推送。監測範圍涵蓋境內和境外微博平臺(境內:新浪、騰訊,境外:twitter)。

因為訪問境外的微博需要經過代理轉發,導致經常有圖片載入不出來,嚴重影響使用者觀感。此時就需要一個錯誤重試的處理,不要讓介面上出現這個圖片載入錯誤的圖示,如下所示:

不需要任何依賴的圖片載入錯誤處理的工具類load-image.js

思路:

通過外部傳進來的代表圖片來源的引數,判斷是否是境外圖片。如果是,則先顯示一張預設圖片,等圖片載入成功後再替換預設圖片。如果圖片載入錯誤了,間隔一段時間進行重試,直到載入成功,或失敗次數大於設定的最大閾值時停止。

預備知識:

圖片載入主要有三種狀態:成功、失敗、載入中(pendding),這個可以在控制檯的NetWork裡面看到。

不需要任何依賴的圖片載入錯誤處理的工具類load-image.js
不需要任何依賴的圖片載入錯誤處理的工具類load-image.js
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狀態,此時是不會有任何返回值的,所以沒有辦法用onloadonerr事件捕獲,定時器一直重複執行,記憶體狂飆!

    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;

複製程式碼

相關文章