為什麼 JS 開發者更喜歡 Axios 而不是 Fetch?

高級前端進階 發佈 2024-04-09T17:26:28.012812+00:00

本文我會解釋為什麼在開發中 Axios 是比 fetch 更好的選擇。不幸的是,Node.js 並沒有內置的 fetch 函數,不過我們可以使用諸如 node-fetch 這樣的 polyfill。

家好,很高興又見面了,我是"高級前端‬進階‬",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點讚、收藏、轉發!

本文我會解釋為什麼在開發中 Axios 是比 Fetch() 更好的選擇。

概述和語法

Fetch

Fetch() 是 Fetch API 中 JavaScript window 對象的一個方法。它是內置的,所以開發者無需進行任何安裝。Fetch() 允許我們在不安裝其他庫的情況下異步請求數據。

fetch(url)
  .then((res) => {
    // 處理響應
  })
  .catch((error) => {
    // 處理錯誤
  });

上面的代碼是一個簡單的基於 fetch() 的 get 請求。fetch() 默認可以接收一個 url 參數,也即用戶請求獲取數據的路徑。之後,fecth() 返回一個 promise,它可能會 resolve 響應對象,也可能會 reject 一個錯誤。

fetch(url, {
  method: "POST",
  headers: {
    "Content-Type": "application/JSON",
  },
  body: JSON.stringify(data),
})
  .then(response => response.json())
  .then(response => console.log(response))   
  .catch(error => console.log(error));

第二個參數是用於配置的選項,它是可選的。如果用戶不傳遞第二個參數,則默認總是進行 get 請求,並從給定 url 那裡下載相關資源。正如我之前提及的,promise 進行 resolve 的只是一個響應對象(譯者註:第一個 response,並且是一個流對象),因此開發者還需要使用其他方法異步獲取實際的響應數據。響應數據類型不同,對應的方法也不同。

  • response.json()
  • response.text()
  • response.blob()
  • response.formData()
  • response.arrayBuffer()

最常用的是 response.json()。

不幸的是,Node.js 並沒有內置的 fetch() 函數,不過我們可以使用諸如 node-fetch 這樣的 polyfill。在瀏覽器版本的 fetch() 和 node-fetch 之間,還存在著不少變種。

Axios

Axios 是一個可以從 Node 端或者瀏覽器端發起 HTTP 請求的 JavaScript 請求庫。作為一個現代化的庫,它基於 Promise API。Axios 有著諸多優點,比如防禦 CSRF 攻擊等。要使用 Axios,開發者必須先進行安裝並通過 CDN、npm、yarn 或者 Bower 導入到項目中。

axios.get(url)
  .then((response) => console.log(response))
  .catch((error) => console.log(error));

上面這段代碼使用了 Axios 的 get 方法,並針對響應和錯誤設置了相應的回調函數。當開發者創建一個配置對象的時候,他們可以定義一系列的屬性。最常用的有 url、baseURL、 params、 auth、headers、responseType 以及 data。

作為響應,Axios 會返回一個 promise,該 promise 會 resolve 響應對象,或者 reject 錯誤對象。響應對象可能有以下屬性:

  • data: 實際的響應體
  • status: 返迴響應的 HTTP 狀態碼,比如 200 或者 404
  • statusText: 文本信息形式的 HTTP 狀態
  • headers: 響應頭,和請求頭類似
  • config: 請求的配置
  • request: XMLHttpRequest (XHR) 對象
axios({
  url: "http://api.com",
  method: "POST",
  header: {
    "Content-Type": "application/json",
  },
  data: { 
      name: "Sabesan", 
      age: 25 
  }
});

使用 fecth() 必須處理兩個 promise,而使用 Axios 則只需要處理一個,並且可以編寫更加簡潔的代碼。

Axios 使用 data 屬性處理數據,而 fetch() 則使用 body 屬性。fetch() 的 data 是經過序列化的,並且 URL 作為參數傳遞,而 Axios 的 URL 則是直接在配置對象中設置。

JSON

Fetch

使用 fetch() 的時候,開發者需要在請求攜帶數據的時候去序列化數據,並且對於響應回來的數據需要調用某些方法進行反序列化。

fetch('url')
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log(error));

在上面這段代碼中,開發者拿到響應數據後還需要調用 response.json() 進行處理。也就是說,這裡有兩個步驟,一個是發出實際的請求,一個是調用 .json() 處理響應數據。

Axios

使用 Axios,開發者直接在請求中攜帶數據或者從響應中獲取數據,無需進行額外處理。

axios.get('url')
    .then((response)=>console.log(response))
    .catch((error)=>console.log(error))

上面的例子中,我們只用到了一個 then。

數據自動轉化是 Axios 非常棒的一個特性。

錯誤處理

Fetch

每次調用 fetch() 方法返迴響應之後,你都需要手動檢查狀態碼是否成功,因為即使是 400 或者 500 這樣的狀態碼,也會被認為是請求成功。在使用 fetch() 的情況下,只有未完成的請求才不會被 resolve。

fetch('url')
    .then((response)=>{
        if(!response.ok){
            throw Error (response.statusText);
        }
        return response.json();
    })
    .then((data)=>console.log(data))
    .catch((error)=>console.log(error))

Fetch() 對於失敗的狀態碼不會拋出錯誤,因此你必須手動檢查 response.ok 屬性。你可以將錯誤檢查封裝成一個函數以提升它的可重用性:

const checkError = response => {
    if (!response.ok) throw Error(response.statusText);
    return response.json();
  };
  
  fetch("url")
    .then(checkError)
    .then(data => console.log(data))
    .catch(error => console.log("error", error));

Axios

但如果使用 Axios ,錯誤處理將會變得非常簡單,因為 Axios 可以準確地捕獲並拋出錯誤。比如說返回 404 響應的時候,promise 會處於 reject 狀態,並返回一個錯誤。這時候,我們可以捕獲這個錯誤,並且檢查具體是什麼錯誤類型。

axios.get('url')
    .then((response)=> console.log(response))
    .catch((error)=>{
        if(error.response){
        // 當狀態碼不是 2xx 類型的時候
        console.log(error.response.data);
        console.log(error.response.status);
        console.log(error.response.headers);
        } else if (error.Request){
            // 當請求發出後沒有收到響應的時候
            console.log(error.request);
        } else {
            // 其它錯誤
            console.log(error.message);
        }
    })

下載進度

當加載大型資源的時候,進度指示器對低網速用戶來說非常有用。在以前,開發者是通過 XMLHttpRequest.onprogress 來跟蹤下載進度的。

Fetch

要在 fetch() 中跟蹤下載進度,我們可以使用 response.body 的相關屬性。這是一個 ReadableStream 對象,它可以逐塊地提供 body,並且隨時告訴我們下載了多少數據。

// 原始碼: https://github.com/AnthumChris/fetch-progress-indicators
const element = document.getElementById('progress');

fetch('url')
  .then(response => {

    if (!response.ok) {
      throw Error(response.status+' '+response.statusText)
    }

    // 確保瀏覽器支持 ReadableStream 
    if (!response.body) {
      throw Error('ReadableStream not yet supported in this browser.')
    }

    // 以字節存儲實體大小
    const contentLength = response.headers.get('content-length');

    // 確保瀏覽器支持 contentLength 
    if (!contentLength) {
      throw Error('Content-Length response header unavailable');
    }

    // 將整型解析為一個 base-10 數字
    const total = parseInt(contentLength, 10);

    let loaded = 0;

    return new Response(

      // 創建並返回一個可讀流
      new ReadableStream({
        start(controller) {
          const reader = response.body.getReader();

          read();
          function read() {
            reader.read().then(({done, value}) => {
              if (done) {
                controller.close();
                return; 
              }
              loaded += value.byteLength;
              progress({loaded, total})
              controller.enqueue(value);
              read();
            }).catch(error => {
              console.error(error);
              controller.error(error)                  
            })
          }
        }
      })
    );
  })
  .then(response => 
    // 基於數據構造一個 blob
    response.blob()
  )
  .then(data => {
    // 將已下載圖片插入到頁面中
    document.getElementById('img').src = URL.createObjectURL(data);
  })
  .catch(error => {
    console.error(error);
  })

function progress({loaded, total}) {
  element.innerHTML = Math.round(loaded/total*100)+'%';
}

上面的代碼展示了在下載圖片的時候,如何使用 ReadableStream 為用戶提供即時反饋。

Axios

在 Axios 中也可以實現進度指示器,並且更加簡單,因為你只需要安裝並使用相應模塊即可,這個模塊就是 Axios Progress Bar。

loadProgressBar();

function downloadFile(url) {
    axios.get(url, {responseType: 'blob'})
      .then(response => {
        const reader = new window.FileReader();
        reader.readAsDataURL(response.data); 
        reader.onload = () => {
          document.getElementById('img').setAttribute('src', reader.result);
        }
      })
      .catch(error => {
        console.log(error)
      });
}

上傳進度

Fetch

在 fetch() 中無法跟蹤上傳進度。

Axios

在 Axios 中可以跟蹤上傳進度。如果你正在開發一個視頻 / 圖片上傳應用,那麼使用 Axios 是個不錯的選擇。

const config = {
    onUploadProgress: event => console.log(event.loaded)
  };

axios.put("/api", data, config);

HTTP 攔截器

當你需要檢查或者修改從客戶端到服務端的 HTTP 請求時,攔截器就可以派上用場了。當然它也可以用在其它地方,比如身份驗證,日誌記錄等。

Fetch

Fetch() 默認並沒有提供 HTTP 攔截器的功能。雖說你可以重寫 fetch() 方法並定義在請求發出時的行為,但比起直接使用 Axios 的功能,這樣需要編寫大量複雜的代碼。如果你想要重寫全局 fetch() 方法並定義自己的攔截器,可以參考下面的代碼:

fetch = (originalFetch => {
    return (...arguments) => {
      const result = originalFetch.apply(this, arguments);
        return result.then(console.log('Request was sent'));
    };
  })(fetch);
  
fetch('url')
    .then(response => response.json())
    .then(data => {
      console.log(data) 
    });

Axios

Axios 的 HTTP 攔截器是它的核心特性之一 —— 也就是說你無需編寫額外代碼去實現攔截器。

// request 攔截器
axios.interceptors.request.use((config)=>{
    console.log('Request was sent');
    return config;
})

// response 攔截器
axios.interceptors.response.use((response) => {
    return response; 
})

axios.get('url')
    .then((response)=>console.log(response))
    .catch((error)=>console.log(error))

在上面這段代碼中, axios.interceptors.request.use() 和 axios.interceptors.response.use() 方法分別定義在 HTTP 請求發出前和響應收到前需要進行的操作。

響應超時處理

Fetch

Fetch() 可以通過 AbortController 接口處理響應超時問題。

const controller = new AbortController();
const signal = controller.signal;
const options = {
  method: 'POST',
  signal: signal,
  body: JSON.stringify({
    firstName: 'Sabesan',
    lastName: 'Sathananthan'
  })
};  
const promise = fetch('/login', options);
const timeoutId = setTimeout(() => controller.abort(), 5000);

promise
  .then(response => {/* handle the response */})
  .catch(error => console.error('timeout exceeded'));

在上面這段代碼中,我們通過 AbortController.AbortController() 構造器創建一個 AbortController 對象。這個對象允許我們延遲終止請求。正如我之前在別的文章提到的, AbortController 有一個 signal 可讀屬性,它可用於與一個請求進行交互或者終止請求。如果服務端沒有在 5 秒內進行響應,那麼就會通過 controller.abort() 終止請求。

Axios

通過使用配置對象中可選的 timeout 屬性,你可以設置一個終止請求的超時時間。

axios({
    method: 'post',
    url: '/login',
    timeout: 5000,    // 5 秒超時時間
    data: {
      firstName: 'Sabesan',
      lastName: 'Sathananthan'
    }
  })
  .then(response => {/* handle the response */})
  .catch(error => console.error('timeout exceeded'))

JavaScript 開發者更喜歡 Axios 而不是 fetch() 的其中一個原因就是前者處理超時問題更加簡單。

並發請求

Fetch

你可以使用內置的 Promise.all() 實現並發請求。只需要傳遞一個包含多個 fetch() 請求的數組作為參數即可,之後通過一個 async 函數處理響應數據。

Promise.all([
  fetch('https://api.github.com/users/sabesansathananthan'),
  fetch('https://api.github.com/users/rcvaram')
])
.then(async([res1, res2]) => {
  const a = await res1.json();
  const b = await res2.json();
  console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
  console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
  console.log(error);
});

Axios

在 Axios 中,我們可以使用 axios.all()。將所有請求放在一個數據中並傳遞給 axios.all() 方法,之後調用 axios.spread() 方法將響應數組中的每個響應數據賦值給單個變量,就像這樣:

axios.all([
  axios.get('https://api.github.com/users/sabesansathananthan'), 
  axios.get('https://api.github.com/users/rcvaram')
])
.then(axios.spread((obj1, obj2) => {
  // Both requests are now complete
  console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
  console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));

向後兼容

向後兼容可以說就是瀏覽器的支持。

Fetch

Fetch() 只支持 Chrome 42+、Safari 10.1+、Firefox 39+ 和 Edge 14+。具體的兼容情況可以查看 Can I Use。為了在不支持 fetch() 的瀏覽器上實現類似它的功能,你可以使用諸如 windows.fetch ( )這樣的 polyfill。

通過 npm 進行安裝:

npm install whatwg-fetch --save

如果由於某些原因你需要訪問 polyfill 的實現,也可以進行導出:

import {fetch as fetchPolyfill} from 'whatwg-fetch'

window.fetch(...)   // 原生瀏覽器版本
fetchPolyfill(...)  // polyfill 版本

記住,在舊瀏覽器中你可能還需要 promise 的 polyfill。

Axios

Axios 和 fetch() 不一樣。它提供了相當全面的瀏覽器支持,即使像 IE11 這樣的舊瀏覽器也可以順利運行 Axios。更具體的兼容情況可以查看 Axios 的 文檔。

結論

對於大部分進行 HTTP 交互的開發場景,Axios 提供了一系列簡單易用的 API。

當然還有不少用於 HTTP 交互的第三方庫,比如基於 window.fetch 的 ky ,這是一個小而優雅的 HTTP 請求庫;比如說基於 XMLHttpRequest 的 superagent,它是一個小型的、漸進式的客戶端 HTTP 請求庫。

但如果應用需要進行大量請求,或者要求請求庫可以高效進行錯誤處理和 HTTP 攔截,那麼 Axios 是更好的選擇。

而對於只需要調用少量 API 的小項目來說,fetch() 也是個不錯的選擇。


參考資料

  • 原文連結:https://betterprogramming.pub/why-javascript-developers-should-prefer-axios-over-fetch-294b28a96e2c
  • 作者:Sabesan Sathananthan
  • 譯文連結:https://markdowner.net/article/177804698764861440
關鍵字: