狠狠色丁香婷婷综合尤物/久久精品综合一区二区三区/中国有色金属学报/国产日韩欧美在线观看 - 国产一区二区三区四区五区tv

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

如何實現 xhr 和 fetch 的加載進度條功能?

freeflydom
2023年11月27日 11:45 本文熱度 1066

想要在 xhr 和 fetch 中獲得數據加載的比例,從而實現一個“真”進度條,你有什么實現思路嗎?

我是渡一前端子辰老師,相信認真閱讀完這篇文章后,這將不再是一個問題!

思考

首先,我們知道數據加載的比例常用在進度條的效果上。

這就意味著我們需要監聽從響應開始到響應完成,這個過程中任意一個時間點上目前加載數據的多少,以及總量的多少。

因為只要知道了目前的量以及總量,我們就能夠得到任意時間點的加載進度。

得到進度之后剩下的就是渲染界面了,這部分就比較簡單了。

那么關鍵點就在于封裝 Ajax 請求,我們如何分別在 xhr 與 fetch 中得到目前量與總量?會遇到什么問題呢?我們先從 xhr 開始。

xhr 中的進度

我們先看一個最常見的 xhr 的封裝。

export function request(options = {}) {

  const { url, method = "GET", data = null } = options;

  return new Promise((resolve) => {

    const xhr = new XMLHttpRequest();

    xhr.addEventListener("readystatechange", () => {

      if (xhr.readyState === xhr.DONE) {

        resolve(xhr.responseText);

      }

    });

    xhr.open(method, url);

    xhr.send(data);

  });

}

這樣的封裝我們無法知曉目前服務器傳輸了多少數據,所有我們來改造一下。

export function request(options = {}) {

  // 首先我們在配置里加入一個 onProgress

  // 這個 onProgress 要傳遞一個函數

  // 沒每當服務器完成了一小段數據的加載之后,我們就會調用這個函數

  // 并且返回目前的加載量以及總量

  const { url, method = "GET", onProgress, data = null } = options;

  return new Promise((resolve) => {

    const xhr = new XMLHttpRequest();

    xhr.addEventListener("readystatechange", () => {

      if (xhr.readyState === xhr.DONE) {

        resolve(xhr.responseText);

      }

    });

    // xhr 給我們提供了一個 progress 事件,這里的 progress 事件只監聽響應。

    // 每當服務器傳輸完一小段數據之后就會觸發 progress 事件

    xhr.addEventListener("progress", (e) => {

      // 在事件 e 里包含了總量與加載量,我們打印到控制臺

      // e.loaded 當前加載量

      // e.total 總量

      console.log(e.loaded, e.total);

    });

    xhr.open(method, url);

    xhr.send(data);

  });

}

可以看到,每一次加載完一小段,都會輸出加載量和總值,那么知道了這兩個數據之后,計算百分比就很簡單了。

我們只需要將數據返回給 onProgress 在界面實現效果就好了。

export function request(options = {}) {

  const { url, method = "GET", onProgress, data = null } = options;

  return new Promise((resolve) => {

    const xhr = new XMLHttpRequest();

    xhr.addEventListener("readystatechange", () => {

      if (xhr.readyState === xhr.DONE) {

        resolve(xhr.responseText);

      }

    });

    xhr.addEventListener("progress", (e) => {

      // 調用 onProgress 并將數據傳遞給它

      onProgress &&

        onProgress({

          loaded: e.loaded,

          total: e.total,

        });

    });

    xhr.open(method, url);

    xhr.send(data);

  });

}

于是我們就得到了這樣一個效果,接下來我們看看 fetch 中如何實現。

fetch 中的進度

我們再來看一個非常簡單的 fetch 封裝。

export function request(options = {}) {

  const { url, method = "GET", data = null } = options;

  return new Promise(async (resolve) => {

    const resp = await fetch(url, {

      method,

      body: data,

    });

    const body = await resp.text();

    resolve(body);

  });

}

因為 fetch 返回的是一個 Promise,它沒有提供任何事件,所以我們獲取到加載量是很困難的,而 Promise 最終只有兩種狀態,要么成功,要么失敗。

我們無法知道從開始到成功或從開始到失敗中間發生了什么事情。

但是我們知道服務器端的響應頭里有一個 Content-Length 字段,這個字段向我們預告了給我們的數據一共有多少個字節。

所以說總得數據量我們是知道的。

關鍵的是當前的加載量我們不知道,那么我們就必須改造一下這個 fetch 的封裝。

在改造之前先給同學說一下流的概念,假設可讀流是一桶水,讀取流就是反復一杯一杯的從桶里盛出水,可讀流被讀取完就是桶里的水被盛完了。

好了,我們來改造一下 fetch。

export function request(options = {}) {

  const { url, method = "GET", data = null } = options;

  return new Promise(async (resolve) => {

    const resp = await fetch(url, {

      method,

      body: data,

    });

    // 因為我們不知道 Promise 中間發生了什么,所以就不能使用這樣的方便時解析響應體了

    // const body = await resp.text();

    // 如果說你熟悉 fetch Api 應該知道,

    // resp 對象里有個屬性叫 body 它代表的就是響應體

    // resp.body 的類型是一個 ReadableStream<Uint8Array> 也就是可讀流

    // 那既然是一個可讀流,我們就通過 getReader() 讀取一下,拿到流的讀取器

    const reader = resp.body.getReader();

    // 我們使用循環來讀取流的數據

    while (1) {

      // 讀取流是需要時間的,所以我們等待一下

      // 返回值是一個對象,我們結構出來得到兩個值

      // value 是當前流的數據,done 是流數據我們是否讀取完畢

      const { value, done } = await reader.read();

      // 如果說取完了就不再循環了

      if (done) {

        break;

      }

      // 我們打印一下流的數據

      console.log("value >>> ", value);

    }

    // 暫時禁用,不讓 Promise 完成

    // resolve(body);

  });

}

可以看到流數據在不停的被打印,每打印一次就像是可讀流里盛出的一杯水,每一杯水的量是不同的,它會根據你的網絡傳輸情況和你系統處理速度有關系,所以我們只要得到這個每一次讀取的量相加在一起,就得到了當前讀取的量。

我們來繼續寫一下。

export function request(options = {}) {

  // 在配置里加入一個 onProgress

  const { url, method = "GET", onProgress, data = null } = options;

  return new Promise(async (resolve) => {

    const resp = await fetch(url, {

      method,

      body: data,

    });

    // 通過 content-length 得到總量

    const total = +resp.headers.get("content-length");

    const reader = resp.body.getReader();

    // 聲明一個變量用來儲存讀取的量

    let loaded = 0;

    // promise 最后的完成需要把所有的數據拼接起來返回

    // 所以定一個變量用來儲存數據拼接的值

    let body = "";

    // 這個數據可能是二進制,那就要使用 arrayBuffer

    // 也可能是文本,就要使用文本解碼器

    // 比如說我們這里是文本,我們先定一個解碼器

    const decoder = new TextDecoder();

    while (1) {

      const { value, done } = await reader.read();

      if (done) {

        break;

      }

      // 每一次讀取都累加起來

      loaded += value.length;

      // 每一次讀取都對數據解碼并拼接起來

      body += decoder.decode(value);

      // 當然在每一次讀取的時候都要像 xhr 一樣,把總量和讀取量返回

      onProgress &&

        onProgress({

          loaded,

          total,

        });

    }

    // Promise 完成并返回數據

    resolve(body);

  });

}

代碼搞定了我們看一下結果。

擴展

下載的進度我們都實現了,那么你有沒有思考過,上傳怎么辦?按照邏輯來說下載和上傳應該是一樣的,就是反著來的而已。

我們先來說 xhr,xhr 中就比較簡單。

// xhr 中給我們提供了一個事件叫 upload

// upload 里有一個事件叫 progress, upload 里的 progress 事件只監聽請求。

// 它的事件 e 里仍然提供了

// e.loaded 和 e.total

// 所以 xhr 中實現上傳就比較簡單

xhr.upload.addEventListener("progress", (e) => {});

我們在來說一下 fetch,遺憾的是 fetch 中實現不了請求進度。

有的同學會說,響應是一個 response 對象,它里邊有 body 可以拿到讀取器,可以一部分一部分的讀,那么請求不就是一個 request 對象嗎?它里邊不也有 body 嗎?不也可以一部分一部分讀嗎?

這是不行的,子辰盡量給同學解釋一下,聽不懂也沒關系。

我們知道,無論是請求或者響應,它的 body 屬性的類型都是一個叫做 ReadableStream 的可讀流。

這種可讀流都有一個特點,就是在同一時間只能被一個人讀取,那么你想想,請求里的流是不是被瀏覽器讀取了?瀏覽器把這個流讀出來,然后發送到了服務器,所以說我們就讀不了了,就是這個問題。

而且瀏覽器在讀的過程中又不告訴我們它讀了多少,但是目前 W3C 正在討論一種方案,這種方案是附帶在 ServiceWorker 里邊的,它里邊有一套 API 叫做,BackgroundFetchManager目前這套 API 里可以實現請求進度的監聽,但是這套 API 還在試驗中,不能用于生產環境。


作者:子辰Web草廬
鏈接:https://juejin.cn/post/7253969759191023675
來源:稀土掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。



該文章在 2023/11/27 11:45:02 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved