fetch的用法、缺陷、常見缺陷處理

fetch號稱是ajax的替代品,它的API是基于Promise設(shè)計的,舊版本的瀏覽器不支持 Promise。

關(guān)于fetch的用法 ,本文就不做介紹了,可以參看官方文檔,可以得到很詳細的介紹 使用Fetch - Web API 接口參考 | MDN

Fetch的代碼結(jié)構(gòu)比起ajax簡單多了,參數(shù)有點像jQuery ajax。但是,一定記住fetch不是ajax的進一步封裝,而是原生js,沒有使用XMLHttpRequest對象。
fetch的優(yōu)點:

  1. 符合關(guān)注分離,沒有將輸入、輸出和用事件來跟蹤的狀態(tài)混雜在一個對象里
  2. 更好更方便的寫法,坦白說,上面的理由對我來說完全沒有什么說服力,因為不管是Jquery還是Axios都已經(jīng)幫我們把xhr封裝的足夠好,使用起來也足夠方便,為什么我們還要花費大力氣去學習fetch?

我認為fetch的優(yōu)勢就是

  1. 語法簡潔,更加語義化
  2. 基于標準 Promise 實現(xiàn),支持 async/await
  3. 同構(gòu)方便,使用 isomorphic-fetch
  4. 更加底層,提供的API豐富(request, response)
  5. 脫離了XHR,是ES規(guī)范里新的實現(xiàn)方式,是js原生方法不需要引入額外的庫。

近在使用fetch的時候,也遇到了不少的問題:fetch是一個低層次的API,你可以把它考慮成原生的XHR,所以使用起來并不是那么舒服,需要進行封裝。

例如:

  1. fetch只對網(wǎng)絡(luò)請求報錯,對400,500都當做成功的請求,服務(wù)器返回 400,500 錯誤碼時并不會 reject,只有網(wǎng)絡(luò)錯誤這些導致請求不能完成時,fetch 才會被 reject。
  2. fetch默認不會帶cookie,需要添加配置項: fetch(url, {credentials: 'include'})
  3. fetch不支持abort,不支持超時控制。
  4. fetch沒有辦法原生監(jiān)測請求的進度,而XHR可以

正對上面幾個問題,我們下面分別來分析,并對其問題進行處理:

1. fetch請求對某些錯誤http狀態(tài)不會reject

這主要是由fetch返回promise導致的,因為fetch返回的promise在某些錯誤的http狀態(tài)下如400、500等不會reject,相反它會被resolve;只有網(wǎng)絡(luò)錯誤會導致請求不能完成時,fetch 才會被 reject;所以一般會對fetch請求做一層封裝。 簡單的理解就是 fetch 只會認為斷網(wǎng)這種情況才會是錯誤的,其他情況比如:404,403等請求錯誤都是認為請求成功了,應(yīng)為它發(fā)起請求并收到了響應(yīng)。

所以我們對返回狀態(tài)進行校驗,然后拋出錯誤,以便返回正常的報錯信息。

對fetch中的錯誤進行拋出,然后對不同的狀態(tài)返回不同的報錯信息

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const errortext = response.statusText;
  const error = new Error(errortext);
  error.name = response.status;
  error.response = response;
  throw error;
}

let fetchChain = fetch(newUrl, newOptions)
    .then(checkStatus)
    .then(response => {
      if (response.status === 204) {
        return {};
      }
      if (newOptions.responseType === 'blob') {
        return response.blob();
      }
      return newOptions.responseType === 'text' ? response.text() : response.json();
    });

上面的代碼中的checkStatus 主要是檢查成功狀態(tài)下的問題處理。其他情況不做處理。

如果要對其他的狀態(tài)進行檢查 需要通過catch來進行異常的捕獲

  。。。
fetchChain = fetchChain.catch(e => {
    const status = e.name;
    // 監(jiān)聽到 401 錯誤 重新登陸
    if (status === 401) {
        // code 
    }

    // 監(jiān)聽到 網(wǎng)絡(luò)請求錯誤
    // https://github.com/github/fetch/issues/201
    if (status === 'TypeError') {
       // code 
    }

    if (status === 501) {
      // 后端正常的報錯/服務(wù)器報錯
    }

    if (status === 403) {
      notification.error({
        message: `${status}`,
        description: '抱歉,您無權(quán)限訪問此功能',
      });
      return;
    }
   
    // 其他錯誤
    notification.error({
      message: `${status}`,
      description: e.message,
    });
  });

上面代碼就實現(xiàn)了 fetch對某些錯誤http狀態(tài)不會reject,同時面對不同狀態(tài)的不同處理。
關(guān)于catch理解可以看對Promise中的resolve,reject,catch理解

2. fetch不支持abort,不支持超時控制。

fetch不像大多數(shù)ajax庫那樣對請求設(shè)置超時timeout,它沒有有關(guān)請求超時的feature,這一點比較蛋疼。所以在fetch標準添加超時feature之前,都需要polyfill該特性。

實際上,我們真正需要的是abort(), timeout可以通過timeout+abort方式來實現(xiàn),起到真正超時丟棄當前的請求。

而在目前的fetch指導規(guī)范中,fetch并不是一個具體實例,而只是一個方法;其返回的promise實例根據(jù)Promise指導規(guī)范標準是不能abort的,也不能手動改變promise實例的狀態(tài),只能由內(nèi)部來根據(jù)請求結(jié)果來改變promise的狀態(tài)。

既然不能手動控制fetch方法執(zhí)行后返回的promise實例狀態(tài),那么是不是可以創(chuàng)建一個可以手動控制狀態(tài)的新Promise實例呢。所以:
實現(xiàn)fetch的timeout功能,其思想就是新創(chuàng)建一個可以手動控制promise狀態(tài)的實例,根據(jù)不同情況來對新promise實例進行resolve或者reject,從而達到實現(xiàn)timeout的功能;

方法一:單純setTimeout方式
var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts) {
    //定義新的fetch方法,封裝原有的fetch方法
    return new Promise(function(resolve, reject) {
        var timeoutId = setTimeout(function() {
            reject(new Error("fetch timeout"));
        }, opts.timeout);
        oldFetchfn(input, opts).then(
            res => {
                clearTimeout(timeoutId);
                resolve(res);
            },
            err => {
                clearTimeout(timeoutId);
                reject(err);
            }
        );
    });
};

當然在上面基礎(chǔ)上可以模擬類似XHR的abort功能:

var oldFetchfn = fetch;
window.fetch = function(input, opts) {
    return new Promise(function(resolve, reject) {
        var abort_promise = function() {
            reject(new Error("fetch abort"));
        };
        var p = oldFetchfn(input, opts).then(resolve, reject);
        p.abort = abort_promise;
        return p;
    });
};
方法二:利用Promise.race方法

Promise.race方法接受一個promise實例數(shù)組參數(shù),表示多個promise實例中任何一個最先改變狀態(tài),那么race方法返回的promise實例狀態(tài)就跟著改變,具體可以參考這里

var oldFetchfn = fetch; //攔截原始的fetch方法
window.fetch = function(input, opts){//定義新的fetch方法,封裝原有的fetch方法
    var fetchPromise = oldFetchfn(input, opts);
    var timeoutPromise = new Promise(function(resolve, reject){
        setTimeout(()=>{
             reject(new Error("fetch timeout"))
        }, opts.timeout)
    });
    retrun Promise.race([fetchPromise, timeoutPromise])
}

通過上面兩種方式發(fā)現(xiàn)可以發(fā)現(xiàn):
timeout不是請求連接超時的含義,它表示請求的response時間,包括請求的連接、服務(wù)器處理及服務(wù)器響應(yīng)回來的時間;

fetch的timeout即使超時發(fā)生了,本次請求也不會被abort丟棄掉,它在后臺仍然會發(fā)送到服務(wù)器端,只是本次請求的響應(yīng)內(nèi)容被丟棄而已; 這樣就會造成了流量的浪費。 關(guān)于怎么正在取消請求可以參考:https://github.com/hjylewis/trashable

參考文章:
Fetch 手動終止
Fetch的數(shù)據(jù)獲取和發(fā)送以及異常處理
fetch的常見問題及其解決辦法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容