曾經有一陣子,JavaScript 社群中流行著「你不需要 jQuery」的口號,社群裏頭嚷嚷著 Fecth API 將會取代這一切。
從今日的角度來看,XMLHttpRequest
確實有許多設計不足之處,首先,一個 XMLHttpRequest
實例肩負著太多任務,包含了事件的註冊、請求標頭的設置、連線的開啟、資料的傳送、請求本體的設置、回應狀態的判斷、回應內容的取得等,完全不符合關切點分離(Separation of Concerns)的原則,而且設定與呼叫順序混亂,像是經常地,開發者會搞不清楚,到底是要呼叫 open
前還是之後設定請求標頭。
就算是 2011 年標準化後的 XMLHttpRequest Level 1 也沒有改變 XMLHttpRequest
的設計,沒有適當地做職責分離也就算了,雖然增加了幾個可註冊的事件,然而依舊是採基本事件模型,而不是類似 DOM Level 2 事件模型那樣,可以註冊多個事件。
過去有不少程式庫試著封裝 XMLHttpRequest
來解決問題,例如,jQuery 的 $.get
、$.post
或 $.ajax
,$.ajax
可使用選項物件來做更多細部設定(在jQuery 3,$.get
、$.post
也可接受選項物件了),透過 $.ajaxSetup
等函式可設定預設值,這些設計非但隱藏了 XMLHttpRequest
的設定細節,也將一些職責從 XMLHttpRequest
中分離出來。
由於 Ajax 的處理天生就是非同步,這與開發者習慣的同步程式碼撰寫方式不同,而在非同步下順序也變得重要時,回呼地獄就會是個大問題,jQuery 3 中 $.ajax
可傳回 Promise
物件,提供了 Ajax 請求時更一致的模式,可以採用像是同步的程式碼來撰寫非同步應用。
從設計的角度來看,Fetch API 就像是集合了過去 Ajax 使用上一些好實踐的集合體,實現了職責分離,建立時可使用選項物件來進行相關設定,實際上,你也可以獨立地建立 Headers
、Request
、Response
實例。
例如,fetch
除了可接受初始物件設定之外:
fetch('POST-1.php', {
method : 'POST',
headers : {
'Content-Type' : 'application/x-www-form-urlencoded'
},
body : reqString
})
也可以接受 Request
實例:
let request = new Request('POST-1.php', {
method : 'POST',
headers : new Headers({
'Content-Type' : 'application/x-www-form-urlencoded'
}),
body : reqString
});
fetch(request);
大部份的情況下,你不需要接觸 Headers
、Request
、Response
等實例,使用選項物件,通常足以應付,然而,如果需要明確的語意,或者是想重用某個設定,甚至是符合某個介面實現,那麼 Headers
、Request
、Response
等實例就會是需要的。
fetch
的傳回值是 Promise
,表面上看來,Fetch 很像在 XMLHttpRequest
上封裝了一層 Promise
,這也是它為什麼經常被拿來與 $.ajax
對比的原因之一,因為模式乍看之下十分類似,不過嚴格來說,$.ajax
做了比較高階的封裝。
舉例來說,$.ajax
的 data
選項指定物件時,會自動進行序列化與請求參數編碼處理,然而使用 fetch
的 body
選項時,如〈簡介 Fetch API〉中看到的範例,必須自行建立、編碼請求參數,這是因為在 Fetch 的規範前言中就清楚指出,Fetch 的定位本來就是低階封裝。
(Fetch 另一個與 XMLHttpRequest
不同的地方是 Streams
的支援,按照規範,回應物件的 body
特性會是個 ReadableStream
,行為上與 Streams 規範中的 ReadableStream
相同,在伺服器的回應過程中,可以透過 ReadableStream
持續讀取瀏覽器已接收之內容,雖然過去也可以使用 XMLHttpRequest
的 responseText
自行處理判斷、讀取想要的資料區段,然而,前者是直接處理串流資料,後者是對整個已取得之回應進行處理,本質上並不相同。)
正因為 Fetch 是基於 Promise
,而 Promise
主要只有三個狀態,只能透過 resolve
、reject
從未定(pending)轉移至滿足(fulfilled)或背棄(rejected)狀態,Promise
實例本身也只有 then
、 catch
兩個方法來處理對應的狀態,在不施加額外設計上,自然也就無法提供逾時、進度處理等功能。
(若瞭解到某個 Fetch 的限制是來自於 Promise
的限制,就可以試著從設計上,依個別需求來來實現特定的方案。)
在瀏覽器支援上,對於不支援 Fetch 的瀏覽器,可以使用 Fetch Polyfill,修補是基於 XMLHttpRequest
,仿造了 Fetch API 介面,不過正因為基於 XMLHttpRequest
,在某些方面功能會受限;在不支援 Promise
的瀏覽器上,除了 Fetch 修補之外,還要加上 Promise
修補(更舊的瀏覽器,像是 IE8/9,還要加上 ES5 修補等)。