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

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

如何實(shí)現(xiàn) xhr 和 fetch 的加載進(jìn)度條功能?

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

想要在 xhr 和 fetch 中獲得數(shù)據(jù)加載的比例,從而實(shí)現(xiàn)一個(gè)“真”進(jìn)度條,你有什么實(shí)現(xiàn)思路嗎?

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

思考

首先,我們知道數(shù)據(jù)加載的比例常用在進(jìn)度條的效果上。

這就意味著我們需要監(jiān)聽從響應(yīng)開始到響應(yīng)完成,這個(gè)過程中任意一個(gè)時(shí)間點(diǎn)上目前加載數(shù)據(jù)的多少,以及總量的多少。

因?yàn)橹灰懒四壳暗牧恳约翱偭浚覀兙湍軌虻玫饺我鈺r(shí)間點(diǎn)的加載進(jìn)度。

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

那么關(guān)鍵點(diǎn)就在于封裝 Ajax 請求,我們?nèi)绾畏謩e在 xhr 與 fetch 中得到目前量與總量?會(huì)遇到什么問題呢?我們先從 xhr 開始。

xhr 中的進(jìn)度

我們先看一個(gè)最常見的 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);

  });

}

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

export function request(options = {}) {

  // 首先我們在配置里加入一個(gè) onProgress

  // 這個(gè) onProgress 要傳遞一個(gè)函數(shù)

  // 沒每當(dāng)服務(wù)器完成了一小段數(shù)據(jù)的加載之后,我們就會(huì)調(diào)用這個(gè)函數(shù)

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

  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 給我們提供了一個(gè) progress 事件,這里的 progress 事件只監(jiān)聽響應(yīng)。

    // 每當(dāng)服務(wù)器傳輸完一小段數(shù)據(jù)之后就會(huì)觸發(fā) progress 事件

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

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

      // e.loaded 當(dāng)前加載量

      // e.total 總量

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

    });

    xhr.open(method, url);

    xhr.send(data);

  });

}

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

我們只需要將數(shù)據(jù)返回給 onProgress 在界面實(shí)現(xiàn)效果就好了。

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) => {

      // 調(diào)用 onProgress 并將數(shù)據(jù)傳遞給它

      onProgress &&

        onProgress({

          loaded: e.loaded,

          total: e.total,

        });

    });

    xhr.open(method, url);

    xhr.send(data);

  });

}

于是我們就得到了這樣一個(gè)效果,接下來我們看看 fetch 中如何實(shí)現(xiàn)。

fetch 中的進(jìn)度

我們再來看一個(gè)非常簡單的 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);

  });

}

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

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

但是我們知道服務(wù)器端的響應(yīng)頭里有一個(gè) Content-Length 字段,這個(gè)字段向我們預(yù)告了給我們的數(shù)據(jù)一共有多少個(gè)字節(jié)。

所以說總得數(shù)據(jù)量我們是知道的。

關(guān)鍵的是當(dāng)前的加載量我們不知道,那么我們就必須改造一下這個(gè) fetch 的封裝。

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

好了,我們來改造一下 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,

    });

    // 因?yàn)槲覀儾恢?Promise 中間發(fā)生了什么,所以就不能使用這樣的方便時(shí)解析響應(yīng)體了

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

    // 如果說你熟悉 fetch Api 應(yīng)該知道,

    // resp 對象里有個(gè)屬性叫 body 它代表的就是響應(yīng)體

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

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

    const reader = resp.body.getReader();

    // 我們使用循環(huán)來讀取流的數(shù)據(jù)

    while (1) {

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

      // 返回值是一個(gè)對象,我們結(jié)構(gòu)出來得到兩個(gè)值

      // value 是當(dāng)前流的數(shù)據(jù),done 是流數(shù)據(jù)我們是否讀取完畢

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

      // 如果說取完了就不再循環(huán)了

      if (done) {

        break;

      }

      // 我們打印一下流的數(shù)據(jù)

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

    }

    // 暫時(shí)禁用,不讓 Promise 完成

    // resolve(body);

  });

}

可以看到流數(shù)據(jù)在不停的被打印,每打印一次就像是可讀流里盛出的一杯水,每一杯水的量是不同的,它會(huì)根據(jù)你的網(wǎng)絡(luò)傳輸情況和你系統(tǒng)處理速度有關(guān)系,所以我們只要得到這個(gè)每一次讀取的量相加在一起,就得到了當(dāng)前讀取的量。

我們來繼續(xù)寫一下。

export function request(options = {}) {

  // 在配置里加入一個(gè) 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();

    // 聲明一個(gè)變量用來儲(chǔ)存讀取的量

    let loaded = 0;

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

    // 所以定一個(gè)變量用來儲(chǔ)存數(shù)據(jù)拼接的值

    let body = "";

    // 這個(gè)數(shù)據(jù)可能是二進(jìn)制,那就要使用 arrayBuffer

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

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

    const decoder = new TextDecoder();

    while (1) {

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

      if (done) {

        break;

      }

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

      loaded += value.length;

      // 每一次讀取都對數(shù)據(jù)解碼并拼接起來

      body += decoder.decode(value);

      // 當(dāng)然在每一次讀取的時(shí)候都要像 xhr 一樣,把總量和讀取量返回

      onProgress &&

        onProgress({

          loaded,

          total,

        });

    }

    // Promise 完成并返回?cái)?shù)據(jù)

    resolve(body);

  });

}

代碼搞定了我們看一下結(jié)果。

擴(kuò)展

下載的進(jìn)度我們都實(shí)現(xiàn)了,那么你有沒有思考過,上傳怎么辦?按照邏輯來說下載和上傳應(yīng)該是一樣的,就是反著來的而已。

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

// xhr 中給我們提供了一個(gè)事件叫 upload

// upload 里有一個(gè)事件叫 progress, upload 里的 progress 事件只監(jiān)聽請求。

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

// e.loaded 和 e.total

// 所以 xhr 中實(shí)現(xiàn)上傳就比較簡單

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

我們在來說一下 fetch,遺憾的是 fetch 中實(shí)現(xiàn)不了請求進(jìn)度。

有的同學(xué)會(huì)說,響應(yīng)是一個(gè) response 對象,它里邊有 body 可以拿到讀取器,可以一部分一部分的讀,那么請求不就是一個(gè) request 對象嗎?它里邊不也有 body 嗎?不也可以一部分一部分讀嗎?

這是不行的,子辰盡量給同學(xué)解釋一下,聽不懂也沒關(guān)系。

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

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

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


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



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