短影片app原始碼,藉助輪詢最佳化互動體驗

云豹科技-苏凌霄發表於2024-09-07

業務背景

在短影片app原始碼前後端資料互動場景下,使用最多的一種方式是客戶端發起 HTTP 請求,等待服務端處理完成後響應給客戶端結果。
但在一些場景下,短影片app原始碼服務端對資料的處理需要較長的時間,比如提交一批資料,對這批資料進行資料分析,將最終分析結果返回給前端。
如果採用一次 HTTP 請求,使用者會一直處於等待狀態,再加上介面不會有進度互動,導致使用者不知何時會處理完成;此外,一旦重新整理頁面或者其他意外情況,使用者就無從感知處理結果。
面對這類場景,可以藉助 「HTTP 輪詢方式」 對互動體驗進行最佳化,具體過程如下:
首先發起一次 HTTP 請求用於提交資料,之後啟動輪詢在一定間隔時間內查詢分析結果,在這期間後臺可將分析進度同步到前端來告知使用者處理進度;此外即使重新整理再次進入頁面還可以透過「輪詢」實時查詢進度結果。
下面,我們來看看程式碼層面看如何實現這類場景。

JS 實現輪詢的方式

在實現程式碼之前,我們需要先明確 JS 實現輪詢的方式有哪些,哪種方式最適合使用。

1. setInterval

作為前端開發人員,提起輪詢第一時間能想到的是計時器 setInterval,它會按照指定的時間間隔不間斷的輪詢執行處理函式。

let index = 1;

setInterval(() => {
  console.log('輪詢執行: ', index ++);
}, 1000);

回過頭來看我們的場景:要輪詢的是 非同步請求(HTTP),請求響應結果會受限制網路或者短影片app原始碼的伺服器處理速度,顯然 setInterval 這種固定間隔輪詢並不適合這個場景。

2. Promise + setTimeout sleep

setInterval 的不足之處在於 輪詢間隔時間 在非同步請求場景下無法保證兩個請求之間的間隔固定。要解決這個問題,可以使用 sleep 睡眠函式來控制間隔時間。
JS 中沒有提供 sleep 相關方法,但可以結合 Promise + setTimeout 來實現。

const sleep = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 1000);
  });
}

sleep 僅控制了輪詢間隔,而輪詢的執行機制需要我們手動根據非同步請求結果來實現,比如下面透過控制 while 迴圈的條件:

const start = async () => {
  let i = 0;
  while (i < 5) {
    await sleep();
    console.log(`第 ${++ i} 次執行`);
  }
}

start();

使用輪詢的時候可以藉助 async/await 同步的方式編寫,提高程式碼閱讀質量。

實現非同步請求輪詢

下面我們透過一個完整示例理解 輪詢非同步請求 的實現及使用注意事項。
首先我們定義兩個變數:index 用於控制何時停止輪詢,timer 則用於實現中斷輪詢。

let index = 1;
let timer = 0;

這裡,我們定義 syncPromise 來模擬非同步請求,可以看作是一次 HTTP 請求,當進行 5 次非同步請求後,會返回 false 表示拿到資料分析結果,停止資料查詢輪詢:

const syncPromise = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`第 ${index} 次請求`);
      resolve(index < 5 ? true : false);
      index ++;
    }, 50);
  })
}

現在,我們實現 pollingPromise 作為 sleep 睡眠函式使用,去控制輪詢的間隔時間,並在指定時間執行非同步請求:

const pollingPromise = () => {
  return new Promise(resolve => {
    timer = setTimeout(async () => {
      const result = await syncPromise();
      resolve(result);
    }, 1000);
  });
}

最後,startPolling 作為開始輪詢的入口,包含以下邏輯:

1)在輪詢前會清除正在進行的輪詢任務,避免出現多次輪詢;
2)如果需要,在開始輪詢時會立刻呼叫非同步請求查詢一次資料結果;
3)最後,透過 while 迴圈根據非同步請求的結果,決定是否繼續輪詢;

const startPolling = async () => {
  // 清除進行中的輪詢,重新開啟計時輪詢
  clearTimeout(timer); // !!! 注意:清除計時器後,會導致整個 async/await 鏈路中斷,若計時器的位置下方還存在程式碼,將不會執行。
  index = 1;
  // 立刻執行一次非同步請求
  let needPolling = await syncPromise();
  // 根據非同步請求結果,判斷是否需要開啟計時輪詢
  while (needPolling) {
    needPolling = await pollingPromise();
  }
  console.log('輪詢請求處理完成!'); // 若非同步請求被 clearTimeout(timer),這裡不會被執行列印輸出。
}

const start = async () => {
  await startPolling();
  console.log('若非同步請求被 clearTimeout(timer),這裡將不會被執行');
}
start();

不過,需要注意的是:一旦清除計時器後,會導致整個 async/await 鏈路中斷,若計時器的位置下方還存在程式碼,將不會執行。
假設當前執行了兩次輪詢被 clearTimeout(timer) 後,從 startPolling 到 start 整個 async/await 鏈路都會中斷,且後面未執行的程式碼也不會被執行。
基於以上規則,非同步輪詢的處理邏輯儘量放在 syncPromise 非同步請求核心函式中完成,避免在開啟輪詢的輔助函式中去實現。

使用輪詢的其他場景

在短影片app原始碼中,使用輪詢的場景還有很多。
通常我們考慮首屏載入速度,會將一些非主要啟動程式的資源改用 動態非同步 的方式去載入。
如果某個頁面渲染時依賴了非同步載入的指令碼資源,就會出現無法拿到資源變數導致報錯情況,因為這個時候其實資源還沒有載入完成。
所以就需要一種手段,在資源載入完成後再讓頁面拿去使用,輪詢指令碼資源變數是否存在可以作為一種處理方式。

/**
 * 輪詢查詢非同步資源的載入狀態
 */
export const pollingAsyncResource = (handler: Function, condition: () => unknown) => {
  let timer = 0;
  if (!!condition()) {
    // 靜態資源已載入完成
    handler();
  } else {
    // 啟動輪詢查詢靜態資源載入狀態
    timer = window.setInterval(() => {
      if (condition()) {
        // 靜態資源已載入完成
        clearInterval(timer);
        handler();
      }
    }, 50);
  }
}

// 使用
pollingAsyncResource(
  () => // 使用資源的具體邏輯, 
  () => window.$
);

在 pollingAsyncResource 中啟動 計時器輪詢 去查詢靜態資源是否載入完成。
引數 handler 是資源載入完成後要執行的具體邏輯;condition 則是判斷資源是否載入完成的條件,一般指令碼資源都採用 umd 模組形式在 window 物件上繫結全域性變數。

以上就是短影片app原始碼,藉助輪詢最佳化互動體驗, 更多內容歡迎關注之後的文章

相關文章