前端工程師都應該知道的 fetch

之前介紹 ES6 Promise 的時候就有提到一些過去的標準應該也可以更新到來支援 Promise,沒想到就看到 WHATWG 的 fetch 了,fetch 就是個 XMLHttpRequest(XHR)的 替代品,幾乎是集了這幾年前端領域 Pattern 之大成。

首先是命名很簡單,和 XHR 完全不一樣,那個時期的網路標準的命名都很繁雜,尤其像是 XML Schema 的那個時期,聽說是找了些語言學家來一起制訂的,那個時期的東西很多都名稱弄的很冗長,當然不可否認這樣有個好處是比較容易理解東西的源由,像 XHR 看名字就可以知道其實主要目的是為了抓 XML,而那個時期會想要抓 XML 大概就是為了 SOAP 協定的 Web Service 吧,只是真的用來抓 XML 的已經很少了,一直用這個名稱早就已經覺得很奇怪了,至於新的 fetch 顧名思義就是為了抓東西用的,反而和現在 XHR 使用的情境很符合,而且命名很簡單,好記,就像是 jQuery 的on取代了addEventListener一樣。(PS: 另外有一個叫 sendBeacon 的是只管送出,不管回來的東西的。)

第二個特點是使用了 Options Object,不過 XHR 倒也不是收很多參數,他的設計是先產生物件後才對它操作:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'test.html');
xhr.setRequestHeader('Tester-Name', 'mike');
xhr.setRequestHeader('Tester-Name ', 'peter');
xhr.send();

雖然沒有搞不清楚參數順序的問題,卻也是多了很多步驟才能達成目標,不過其實產生了 XHR 物件但是卻不送出 request 的使用情境我實在想不太到,大概是因此,新的 fetch 才改成像是 jQuery 的$.ajax那樣,產生物件時直接就發出 request 了吧。

第三個特點當然就是回傳的是 ES6 Promise 物件,另外也支援 FormData 等等新東西,不過要說能不能完全取代 XHR 呢?目前看起來是不行的,最主要是因為 ES6 Promise 並沒有支援 progress 的機制,而且已經不是 event-based 的物件了,所以沒辦法抓上傳進度之類的資訊。

因為這個 spec 還很新,目前是沒瀏覽器支援,不過 Github 有提供一個 polyfill 了,把基本的功能都做好了(還有缺一些比較少用到的細節),有興趣想開始用的人可以從這邊開始,大概要注意的有兩個,第一個是因為它是用 ES6 Promise,所以還要引入 ES6 Promise polyfill,第二個是回傳資料的處理,雖然 fetch 在發 request 的時候和 jQuery 的設計很像,不過回傳的資料處理方式就差距比較大了。

jQuery 的 ajax 收到 Response Body 時,會自動根據 Header 的 Content-Type 來處理,像是 JSON 會自動用JSON.parse把文字轉成 JS 物件,不過 fetch 不會,根據 spec 所說, fetch 算是一個底層的 library,所以這種事情就要自己來了:

fetch("https://pk.example/berlin-calling.json", {mode:"cors"})
  .then(res => {
  if(res.headers.get("content-type") == "application/json") {
    return res.json()
  } else {
    throw new TypeError()
  }
}).then(processJSON)

fetch 需要你自己在程式碼裡面判斷回傳資料的格式是什麼,然後可以用它提供的 method 擷取到相對應格式的資料,像是這個例子中抓的是 JSON 格式的資料,就直接執行 Response 物件的json這個 method,當然你也可以不判斷就直接執行json(),只是無法 parse 時會直接 throw error 出來,又因為在 Promise 串接過程中,後面就會跑到 reject 的 callback function 那邊去,除了json外,其他支援的還有arrayBufferblobformDatatext。這些從 response 物件中讀取 body 資料出來的動作(spec 中稱為 consume)只能操作一次,如果真的很想讀很多次,建議是直接把回傳資料的那個 Promise 儲存起來,還有一個方法是用 clone 複製 Response 物件,不確定那個方法好就是了,這部分這樣設計的原因似乎是為了處理少一點事情,讓效能比較好。

而除了 Response Body 外,其它的回傳資訊像是 Response Header 等,都有新定義的物件來儲存,不過沒有很複雜,設計的很直覺,和送出去的 Options Object 很接近。不過講到 Header 就有一點還是要說一下,其實 HTTP Header 是可以重複送出一樣的 key 的,先不管合不合規範,現實是 HTTP Protocol 的實作都還可以處理這種狀況,以前的 XHR 也可以做出這樣的行為,印象中也有 framework 會這樣用,不過不太確定,總之 fetch 因為 Header 是給 Options Object 中的一個物件,而物件的 key 不能重複,所以不會允許這種行為出現,我個人是覺得這樣其實也比較好啦。

目前這個標準還未廣為人知,但是我是覺得前景非常看好,Spec 寫的也異常詳細,雖然不能把 XHR 的所有功能都取代,不過大部分的 XHR 應用都可以用的上了,也有 Github 提供的 polyfill,應該很容易吸引人進去使用,加上也沒其它的類似候選標準,除了沒有 progress 和回來的資料格式要自己判斷外,應該是沒什麼缺點了,而且判斷資料格式的部分也是可以自己寫點程式碼把他處理掉,所以嚴格一點說的話,問題就剩下沒有 progress 可以看這點了。