Fetch() 簡介

Fetch() API 到達視窗物件中,想要取代 XHR

Matt Gaunt

那麼長的 XMLHttpRequest

fetch() 可讓您發出類似 XMLHttpRequest (XHR) 的網路要求。主要的差別在於 Fetch API 使用 Promise,這不僅可啟用更簡潔、更簡潔的 API,不但能避免回呼堆積,也不必記住 XMLHttpRequest 的複雜 API。

瀏覽器支援

  • 42
  • 14
  • 39
  • 10.1

資料來源

自 Chrome 40 版以來,Service Worker 的全域範圍就已提供 Fetch API,但會在 Chrome 42 的視窗範圍內啟用。另外,您也可以立即使用 GitHub 提供的 polyfill

如果您從未使用過 Promise,請參閱 JavaScript Promise 簡介

基本擷取要求

首先,比較使用 XMLHttpRequestfetch 實作的簡易範例。我們只想要求網址、取得回應,並將網址剖析為 JSON。

XMLHttpRequest

XMLHttpRequest 需要設定兩個事件監聽器來處理成功和錯誤情況,以及呼叫 open()send()MDN 文件範例

function reqListener() {
    var data = JSON.parse(this.responseText);
    console.log(data);
}

function reqError(err) {
    console.log('Fetch Error :-S', err);
}

var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.onerror = reqError;
oReq.open('get', './api/some.json', true);
oReq.send();

擷取

我們的擷取要求看起來像這樣:

fetch('./api/some.json')
    .then(
    function(response) {
        if (response.status !== 200) {
        console.log('Looks like there was a problem. Status Code: ' +
            response.status);
        return;
        }

        // Examine the text in the response
        response.json().then(function(data) {
        console.log(data);
        });
    }
    )
    .catch(function(err) {
    console.log('Fetch Error :-S', err);
    });

首先,我們先檢查回應狀態是否為 200,然後再將回應剖析為 JSON。

fetch() 要求的回應是串流物件,這表示我們呼叫 json() 方法時,會傳回 Promise,因為串流的讀取作業會以非同步方式進行。

回應中繼資料

在先前的範例中,我們查看了 Response 物件的狀態,以及如何將回應剖析為 JSON。以下為我們可能想存取的其他中繼資料 例如標頭

fetch('users.json').then(function(response) {
    console.log(response.headers.get('Content-Type'));
    console.log(response.headers.get('Date'));

    console.log(response.status);
    console.log(response.statusText);
    console.log(response.type);
    console.log(response.url);
});

回應類型

當我們提出擷取要求時,回應會提供「basic」、「cors」或「opaque」的 response.type。這些 types 表示資源的來源,可用來指出應如何處理回應物件。

對相同來源上的資源提出要求時,回應會具有 basic 類型,而且回應上可查看的內容沒有限制。

如果在其他來源上針對資源發出要求,且傳回COR 標頭,則類型為 corscorsbasic 回應幾乎完全相同,差別在於 cors 回應會限制您可查看的標頭為 Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma

opaque 回應適用於針對不同來源的資源,該資源不會傳回 CORS 標頭的要求。如果使用不透明的回應,我們將無法讀取傳回的資料或查看要求的狀態,因此無法檢查要求是否成功。

您可以定義擷取要求的模式,只解析特定要求。您可以設定的模式如下:

  • same-origin 只會針對相同來源上的資產要求成功,其他所有要求都會遭到拒絕。
  • cors 會允許要求位於相同來源和其他來源上,且傳回適當 COR 標頭的資產。
  • cors-with-forced-preflight 一律會在提出實際要求之前執行預檢
  • no-cors 的作用是向其他來源發出要求,但這些來源沒有 CORS 標頭並產生 opaque 回應,但如前所述,目前在視窗全域範圍中無法辦到。

如要定義模式,請將選項物件新增為 fetch 要求的第二個參數,並在該物件中定義模式:

fetch('http://some-site.com/cors-enabled/some.json', {mode: 'cors'})
    .then(function(response) {
    return response.text();
    })
    .then(function(text) {
    console.log('Request successful', text);
    })
    .catch(function(error) {
    log('Request failed', error)
    });

鏈結狀態

承諾的一大優勢是能夠將兩者鏈結在一起。以擷取來說,這可讓您在不同的擷取要求之間共用邏輯。

如果您使用的是 JSON API,您必須檢查狀態,並剖析每個回應的 JSON。您可以在傳回承諾的個別函式中定義狀態和 JSON 剖析,藉此簡化程式碼,這樣您就不必費心處理最終資料和錯誤情況。

function status(response) {
    if (response.status >= 200 && response.status < 300) {
    return Promise.resolve(response)
    } else {
    return Promise.reject(new Error(response.statusText))
    }
}

function json(response) {
    return response.json()
}

fetch('users.json')
    .then(status)
    .then(json)
    .then(function(data) {
    console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
    console.log('Request failed', error);
    });

我們會定義用來檢查 response.statusstatus 函式,並傳回 Promise.resolve()Promise.reject() 的結果,後者會傳回已解決或遭拒的 Promise。這是我們 fetch() 鏈結中呼叫的第一個方法,如果可以解析,我們就會呼叫 json() 方法,再次從 response.json() 呼叫傳回 Promise。之後我們會產生剖析的 JSON 物件。如果剖析失敗,Promise 會遭到拒絕,而擷取陳述式便會執行。

最棒的是,您可以在所有擷取要求之間共用邏輯,進而更輕鬆地維護、讀取及測試程式碼。

POST 要求

網頁應用程式通常想使用 POST 方法呼叫 API,並在要求主體中提供部分參數。

方法是在 fetch() 選項中設定 methodbody 參數。

fetch(url, {
    method: 'post',
    headers: {
        "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
    })
    .then(json)
    .then(function (data) {
    console.log('Request succeeded with JSON response', data);
    })
    .catch(function (error) {
    console.log('Request failed', error);
    });

使用擷取要求傳送憑證

如果您想使用 Cookie 等憑證提出擷取要求,應將要求的 credentials 設為 "include"

fetch(url, {
    credentials: 'include'
})

常見問題

如何取消 fetch() 要求?

目前無法取消擷取作業,但需要在 GitHub 上探討,這個連結的 H/T @jaffathecake

是否有 polyfill?

GitHub 有可擷取的 polyfill。H/T @Nexii

為什麼服務工作處理程序支援「沒有線路」功能,但不支援窗口?

這與安全考量的關係,請按這裡瞭解詳情。