標籤:

【譯】為什麼我不再使用Fetch API開發應用

原文鏈接:Why I won』t be using Fetch API in my apps

作者:Shahar Talmi

當 fetch api 成為 web 標準時,我很激動,因為我再也不需要使用一些 http 工具庫來做 http 請求了。XMLHttpRequest 太底層而且難以使用(它連名字都很詭異,為啥 XML 大寫而 Http 不大寫??)。你不得不自己封裝它,或是從一大堆封裝好的替代品中選擇一個來使用,比如 jQuery 的 $.ajax,Angualr 的 $http,superagent 以及我的最愛-- axios。然而,我們真的就此擺脫了 http 工具庫嗎?

有了 fetch,我再也不用從這一大堆工具庫中做選擇,也不用和同事爭論到底哪一個是最好的了。我只需要引入一個 fetch polyfill,然後就可以愉快地使用標準的 api 了,它可是從眾多用例和經驗教訓中總結設計出來的。

但是,當我們考察一些非常基本的實際場景時,會發現 http 工具庫還是有用武之地的。fetch 是一個廣受歡迎的新特性,能夠幫助我們輕鬆地做一些底層操作,它就是為此而設計的。作為一個底層的 api,儘管抽象得更為合理,但在大多數應用中,我們不應該直接使用它。

錯誤處理

在一些簡單的 fetch 示例中,fetch 看起來非常棒,它和我們習慣使用的 http 工具庫很相似。比如這個使用 axios 的例子:

axios.get(url)n .then(result => console.log(success:, result))n .catch(error => console.log(error:, error));n

我們可以用 fetch 改寫成

fetch(url).then(response => response.json())n .then(result => console.log(success:, result))n .catch(error => console.log(error:, error));n

很簡單,對吧?細心的讀者可能發現,我們需要加上一句 response.json() 來從 response 流對象中獲取數據,但這只是一點很小的代價。我個人認為需要響應流只是一種特殊情況,通常在設計 api 時,我不會讓特殊情況影響到通用情況,我會更傾向於允許用戶提供一個標誌,表明他們是否需要一個流,而不是硬塞給用戶一個流對象。但總的來說,這不是什麼大問題。

上例中真正重要的地方可能大家沒有注意到(就像我第一次使用 fetch 時那樣),那就是實際上這兩段代碼做的根本不是同一件事!之前我提到的所有 http 工具庫會把狀態碼錯誤的響應(比如404,500等)當成一個錯誤來處理,而 fetch 與 XMLHttpRequest 一樣,只會在網路錯誤的情況下(比如 IP 地址無法解析,伺服器不可訪問或是不允許 CORS)reject 這個 promise。

這意味著當伺服器返回404的時候,第二段代碼會列印出 success。如果想讓上述代碼的行為更符合直覺,在伺服器返回錯誤碼的時候,得到一個被 reject 的 promise,我們需要這樣做:

fetch(url)n .then(response => {n return response.json().then(data => {n if (response.ok) {n return data;n } else {n return Promise.reject({status: response.status, data});n }n });n })n .then(result => console.log(success:, result))n .catch(error => console.log(error:, error));n

我敢肯定很多人要問了:「弄啥嘞?你向伺服器發出請求並且得到了響應,管他是不是404呢,它確實是一個伺服器返回的響應啊,為什麼要和網路錯誤一樣對待?」他們說得對,這只是一個視角的問題。我認為從一個開發者的角度,一個錯誤的響應應該和網路錯誤一樣,被當做異常來對待。為了修復 fetch 的這種行為,我們只能這麼做,因為沒法改變 fetch 的標準行為。顯然我們需要一種對開發者來說更合適的抽象。

POST 請求

另一種很常見的情形是向伺服器發出一個 post 請求。藉助於 axios,我們可以這樣寫:

axios.post(/user, {n firstName: Fred,n lastName: Flintstonen});n

當我剛開始使用 fetch api 時,我真是太樂觀了,我當時想:太棒了,這個新 api 和我習慣使用的如此相似。然而,我最終浪費了幾乎一個小時才成功發出一個 post 請求,因為這段代碼並不能工作:

fetch(/user, {n method: POST,n body: {n firstName: Fred,n lastName: Flintstonen }n});n

我相信很多人和我一樣,有過這樣痛苦的經歷後才能意識到,fetch 是一種底層的 api,它不會在我們處理這種一般情形時帶來便利,你必須清楚明確地使用它。首先,JSON 必須先轉換成字元串,然後還要設置 Content-Type 頭部,指出實體的類型是 JSON,否則伺服器會把它當做普通的字元串處理。我們應該這麼寫:

fetch(/user, {n method: POST,n headers: {n Content-Type: application/jsonn },n body: JSON.stringify({n firstName: Fred,n lastName: Flintstonen })n});n

好吧,對我來說,每次使用 fetch api 時寫的這段代碼實在是太長了。然而接下來你會看到,我們還得寫更多!

默認行為

就像你看到的,你必須清楚明確地使用 fetch,如果你不寫明你要什麼,那麼你什麼也獲取不到。舉個栗子,上面提到的所有 fetch 調用都沒法從我的伺服器上獲取到數據,因為:

  1. 我的伺服器使用基於 cookie 的認證方式,而 fetch 默認情況下不會發送 cookie
  2. 我的伺服器需要知道客戶端是否可以處理 JSON 數據
  3. 我的伺服器在另一個子域名下,而 fetch 默認不啟用 CORS
  4. 為了防禦 XSRF 攻擊,我的伺服器要求每一個請求都必須帶上一個 X-XSRF-TOKEN 頭部,來證明請求確實是從我自己的頁面發出的

所以,我應該這麼寫:

fetch(url, {n credentials: include,n mode: cors,n headers: {n Accept: application/json,n X-XSRF-TOKEN: getCookieValue(XSRF-TOKEN)n }n});n

不能說 fetch 的這種默認行為有問題,但如果我要在應用中多處發起請求,我需要一種能夠改變這種默認行為的機制,使得 fetch 能在我的應用中正常工作。遺憾的是,fetch 並沒有這種覆蓋默認行為的機制。你可能已經猜到了,axios 里有:

axios.defaults.baseURL = https://api.example.com;naxios.defaults.headers.common[Accept] = application/json;naxios.defaults.headers.post[Content-Type] = application/json;n

不過這只是為了演示,因為實際上上面提到包括 XSRF 防禦在內的功能,都是 axios 默認提供的。axios 設計的目的是提供一種易用的向伺服器發起請求的工具,而 fetch 必須設計得更為通用,這就是為什麼它不是完成這項工作的最佳工具。

總結

假設你不使用一個 http 工具庫,意味著相比於寫這樣一行代碼:

function addUser(details) {n return axios.post(https://api.example.com/user, details);n}n

你得這麼寫:

function addUser(details) {n return fetch(https://api.example.com/user, {n mode: cors,n method: POST,n credentials: include,n body: JSON.stringify(details),n headers: {n Content-Type: application/json,n Accept: application/json,n X-XSRF-TOKEN: getCookieValue(XSRF-TOKEN)n }n }).then(response => {n return response.json().then(data => {n if (response.ok) {n return data;n } else {n return Promise.reject({status: response.status, data});n }n });n });n}n

每次 api 調用的時候都重複這麼多代碼顯然不是個好主意。你可能會從中抽取出一個函數,交給項目中的同事使用,而不是直接使用 fetch。

當進行下一個項目時,你可能會將那個函數進一步封裝成一個庫。然後當更多的需求到來時,你會嘗試精簡 api、將它設計得更靈活、修復一些 bug,或是讓你的 api 保持一致。你可能還會增加一些新特性,比如中斷請求、自定義超時時間等。

你可能會完成一件非常棒的工作。但是你所做的只不過是創造了另一個 http 工具庫,用它來代替 fetch api 在項目中使用。那還不如直接敲下 npm install --save axios,或是安裝另一個你喜歡的 http 工具庫,這會節約你大量的時間和精力。

另外,仔細想想,你會在意這個 http 工具庫在內部使用的是 fetch 還是 XMLHttpRequest 嗎?

P.S.

我只是想再強調一下:我可不是說 fetch 有多糟糕!我認為上面提到的那些點並不是 fetch api 設計上的缺陷,對於一個底層 api 來說這些設計是完全合理的。我只是不推薦直接在應用中使用像 fetch 這樣的底層 api。人們應該使用那些對底層進行了抽象,提供了高層 api 的工具,那會更符合他們的需求。


推薦閱讀:

前端有哪些好的學習網站?
web前端需要掌握哪些ps的操作呢?
我的第一個響應式頁面

TAG:前端开发 |