XMLHttpRequest
一直是web開發者的親密助手。無論是直接的,還是間接的, 當我們談及Ajax技術的時候,通常意思就是基於XMLHttpRequest
的Ajax,它是一種能夠有效改進頁面通訊的技術。 Ajax的興起是由於Google的Gmail所帶動的,隨後被廣泛的應用到眾多的Web產品(應用)中,可以認為, 開發者已經預設將XMLHttpRequest
作為了當前Web應用與遠端資源進行通訊的基礎。 而本文將要介紹的內容則是XMLHttpRequest
的最新替代技術——Fetch API, 它是W3C的正式標準,本文將會介紹Fetch API的相關知識,以及探討它所能使用的場景和能解決的問題。
Statement
原文地址: http://www.sitepoint.com/introduction-to-the-fetch-api/
譯者:景莊,Web開發工程師,主要關注於前端工程化技術、Node.js、React等。
Fetch API
Fetch API提供了一個fetch()
方法,它被定義在BOM的window
物件中,你可以用它來發起對遠端資源的請求。 該方法返回的是一個Promise物件,讓你能夠對請求的返回結果進行檢索。
為了能夠進一步的解釋Fetch API,下面我們寫一些程式碼來具體的介紹它的用法: 下面這個例子將會通過Flicker API來檢索一些圖片,並將結果插入到頁面中。到目前為止, Fetch API還未被所有的瀏覽器支援。因此,如果你想體驗這一技術,最好使用最新版本的Chrome瀏覽器。 為了能夠正確的呼叫Flicker API,你需要申請自己的API KEY,將其插入到程式碼中的適當位置,即your_api_key
那個位置。
來看看第一個任務:我們使用API來從Flicker中檢索一些有關”企鵝“的照片,並將它們展示在也沒中,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 |
/* API URL, you need to supply your API key */ var URL = 'https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=your_api_key&format=json&nojsoncallback=1&tags=penguins'; function fetchDemo() { fetch(URL).then(function(response) { return response.json(); }).then(function(json) { insertPhotos(json); }); } fetchDemo(); |
上面的程式碼看起來很簡單:首先是構造請求的URL,然後將URL傳遞給全域性的fetch()
方法,它會立刻返回一個Promise, 當Promise被通過,它會返回一個Response
物件,通過該物件的json()
方法可以將結果作為JSON物件返回。 response.json()
同樣會返回一個Promise
物件,因此在我們的例子中可以繼續連結一個then()
方法。
為了能夠和傳統的XMLHttpRequest
進行對比,我們使用傳統的方法來編寫一個同樣功能的函式:
1 2 3 4 5 6 7 8 |
function xhrDemo() { var xhr = new XMLHttpRequest(); xhr.onload = function() { insertPhotos(JSON.parse(xhr.responseText)); }; xhr.open('GET', URL); xhr.send(); } |
可以發現,主要的不同點在於:傳統上我們會使用事件處理器,而不是Promise物件。 並且請求的發起完全依賴於xhr
物件所提供的方法。
到目前為止,相比傳統的XMLHttpRequest
物件,我們使用Fetch API獲得了更簡潔的編碼體驗。但Fetch API不止於此, 下面我們進一步的深入下去。
為什麼需要替代XMLHttpRequest
看了前面的例子,你可能會問,為什麼不直接使用那些現有的XMLHttpRequest
包裝器呢? 原因在於Fetch API不僅僅為你提供了一個fetch()
方法。
對於傳統的XMLHttpRequest
而言,你必須使用它的一個例項來執行請求和檢索返回的響應。 但是通過Fetch API,我們還能夠明確的配置請求物件。
你可以通過Request
構造器函式建立一個新的請求物件,這也是建議標準的一部分。 第一個引數是請求的URL,第二個引數是一個選項物件,用於配置請求。請求物件一旦建立了, 你便可以將所建立的物件傳遞給fetch()
方法,用於替代預設的URL字串。示例程式碼如下:
1 2 3 4 5 6 |
var req = new Request(URL, {method: 'GET', cache: 'reload'}); fetch(req).then(function(response) { return response.json(); }).then(function(json) { insertPhotos(json); }); |
上面的程式碼中我們指明瞭請求使用的方法為GET
,並且指定不快取響應的結果。
有關Request
物件的另一件更酷的事在於,你還可以基於原有的物件建立一個新的物件。 新的請求和舊的並沒有什麼不同,但你可以通過稍微調整配置物件,將其用於不同的場景。 例如,你可以基於原有的GET請求建立一個POST請求,它們具有相同的請求源。程式碼如下:
1 2 |
// 基於req物件建立新的postReq物件 var postReq = new Request(req, {method: 'POST'}); |
每個Request
物件都有一個header
屬性,在Fetch API中它對應了一個Headers
物件。 通過Headers
物件,你能夠修改請求頭。不僅如此,對於返回的響應,你還能輕鬆的返回響應頭中的各個屬性。 但是需要注意的是,響應頭是隻讀的。
1 2 3 4 5 6 7 |
var headers = new Headers(); headers.append('Accept', 'application/json'); var request = new Request(URL, {headers: headers}); fetch(request).then(function(response) { console.log(response.headers); }); |
在上面的程式碼中,你可以通過Headers
構造器來獲取這個物件,用於為新的Request
物件配置請求頭。
相似的,你可以建立一個Response
物件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function responseDemo() { var headers = new Headers({ 'Content-Type': 'application/json', 'Cache-Control': 'max-age=3600' }); var response = new Response( JSON.stringify({photos: {photo: []}}), {status: 200, headers: headers} ); response.json().then(function(json) { insertPhotos(json); }); } |
Request
和Response
都完全遵循HTTP標準。如果你曾經使用過某種伺服器端語言,你應該對它們很熟悉。 但是對於瀏覽器而言建立HTTP響應的要點是什麼?總之,你不能將它傳送給其他人。但是, 你可以通過Service Worker API將響應傳送給你自己。 Service Worker允許通過擷取來自瀏覽器的請求頭和提供本地構造的響應頭來替換來自伺服器的響應頭的方式來構建離線應用。 你需要注意的是,在本文寫作的時候Service Worker仍然是實驗性的,並且仍處在不斷變化之中。
Fetch API面臨的阻力
Fetch API從提出到實現一直存在著爭議,由於一直現存的歷史原因(例如HTML5的拖拽API被認為太過稀疏平常,Web Components標準被指意義不大)。 因此重新設計一個新的API來替代久經沙場歷練的XMLHttpRequest
就變得阻力重重。
其中一種反對觀點認為,Promises缺少了一些重要的XMLHttpRequest
的使用場景。例如, 使用標準的ES6 Promise你無法收集進入資訊或中斷請求。而Fetch的狂熱開發者更是試圖提供Promise API的擴充套件用於取消一個Promise。 這個提議有點自挖牆角的意思,因為將這將讓Promise變得不符合標準。但這個提議或許會導致未來出現一個可取消的Promise標準。 但另一方面,使用XMLHttpRequest
你可以模擬進度(監聽progress
事件),也可以取消請求(使用abort()
方法)。 但是,如果有必要你也可以使用Promise來包裹它。
另一種反對觀點認為,Web平臺需要的是更多底層的API,而不是高層的API。對此的回答恰恰是, Fetch API足夠底層,因為當前的WHATWG標準定義了XMLHttpRequest.send()
方法其實等同於fetch的Requset
物件。 Fetch中的Response.body
實現了getReader()
方法用於漸增的讀取原始位元組流。 例如,如果照片列表過大而放不進記憶體的話,你可以使用下面的方法來處理:
1 2 3 4 5 6 7 8 9 10 11 |
function streamingDemo() { var req = new Request(URL, {method: 'GET', cache: 'reload'}); fetch(req).then(function(response) { var reader = response.body.getReader(); return reader.read(); }).then(function(result, done) { if (!done) { // do something with each chunk } }); } |
在上面的程式碼中處理器函式一塊一塊的接收響應體,而不是一次性的。當資料全部被讀完後會將done
標記設定為true。 在這種方式下,每次你只需要處理一個chunk,而不是一次性的處理整個響應體。
不幸的是,對於Stream API而言,這仍然還處於早期階段,這種方式下,如果你需要解析JSON, 你仍然需要從頭實現很多的工作。
瀏覽器支援
目前Chrome 42+, Opera 29+, 和Firefox 39+都支援Fetch。微軟也考慮在未來的版本中支援Fetch。 諷刺的是,當IE瀏覽器終於微響應實現了progress事件的時候,XMLHttpRequest
也走到了盡頭。 目前,如果你需要支援IE的話,你需要使用一個polyfill庫。
總結
在本文中我們為你介紹了Fetch API的整體概況以及它所能解決的問題。在表層, 這個API看起來非常的簡單,但它同時也與一些底層的API相關聯,例如Streams, 這讓客戶端程式設計有點類似於系統程式設計。
此外,本文中的程式碼示例你可以參考這個倉庫。