深入淺出Fetch API

jopen 10年前發布 | 83K 次閱讀 前端技術

多年來,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/

譯者:景莊,前端開發工程師,主要關注于前端工程化技術、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中檢索一些有關”企鵝“的照片,并將它們展示在也沒中,代碼如下。

/* 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進行對比,我們使用傳統的方法來編寫一個同樣功能的函數:

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字符串。示例代碼如下:

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請求,它們具有相同的請求源。代碼如下:

// 基于req對象創建新的postReq對象
var postReq = new Request(req, {method: 'POST'});

每個Request對象都有一個header屬性,在Fetch API中它對應了一個Headers對象。 通過Headers對象,你能夠修改請求頭。不僅如此,對于返回的響應,你還能輕松的返回響應頭中的各個屬性。 但是需要注意的是,響應頭是只讀的。

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對象:

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);
    });
}

RequestResponse都完全遵循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()方法用于漸增的讀取原始字節流。 例如,如果照片列表過大而放不進內存的話,你可以使用下面的方法來處理:

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, 你仍然需要從頭實現很多的工作。

瀏覽器支持

Fetch Support

Fetch API

目前Chrome 42+, Opera 29+, 和Firefox 39+都支持Fetch。微軟也考慮在未來的版本中支持Fetch。 諷刺的是,當IE瀏覽器終于微響應實現了progress事件的時候,XMLHttpRequest也走到了盡頭。 目前,如果你需要支持IE的話,你需要使用一個polyfill庫。

總結

在本文中我們為你介紹了Fetch API的整體概況以及它所能解決的問題。在表層, 這個API看起來非常的簡單,但它同時也與一些底層的API相關聯,例如Streams, 這讓客戶端編程有點類似于系統編程。

此外,本文中的代碼示例你可以參考這個倉庫

References

  1. 傳統Ajax已死,Fetch永生
  2. That’s so Fetch!
  3. Ain’t that fetch!
  4. Fetch API on MDN