Fetch

Why Fetch

XMLHttpRequest是一個設計粗糙的API,不符合關注分離(Separation of Concerns)的原則,配置和調用方式很是混亂,並且基於事件的異步模型寫起來也沒有現代的Promise,generator/yield,async/await友好。es6

Fetch的出現就是爲了解決XHR的問題。
傳統使用XHR發送一個json請求通常是這樣ajax

var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';

xhr.onload = function () {
    console.log(xhr.response);
}
xhr.onerror = function () {
    console.log('Oops, error');
}
xhr.send();

使用Fetch後json

fetch(url).then(function (response) {
    return response.json();
}).then(function (data) {
    console.log(data);
}).catch(function (e) {
    console.log('Oops, error')
})

使用ES6的箭頭函數後:跨域

fetch(url).then(response => response.json())
    .then(data => console.log(data))
    .catch(e => console.log('Oops, error', e))

但這種Promise的寫法仍是有callback的影子,並且promise使用catch方法進行錯誤處理有點奇怪。
使用async/await進行優化(屬於ES7)數組

try {
    let response = await fetch(url);
    let data = response.json();
    console.log(data);
} catch (e) {
    console.log('Oops, error', e);
}

// 注:這段代碼若是想運行,外面須要包一個async function

使用await後,寫異步代碼就像寫同步代碼同樣。await後面能夠跟Promise對象,表示等待Promise的resolve()才繼續向下執行,若是Promise被reject()或拋出異常則被外面的try...catch捕獲promise

Fetch優勢:
1. 語法簡潔,更加語義化
2. 基於標準Promise實現,支持 async/await
3. 同構方便,使用 isomorphic-fetch瀏覽器

Fetch啓用方法

  1. 因爲 IE8 是 ES3,須要引入 ES5 的 polyfill: es5-shim, es5-sham
  2. 引入 Promise 的 polyfill: es6-promise
  3. 引入 fetch 探測庫:fetch-detector
  4. 引入 fetch 的 polyfill: fetch-ie8
  5. 可選:若是你還使用了 jsonp,引入 fetch-jsonp
  6. 可選:開啓 Babel 的 runtime 模式,如今就使用 async/await

Fetch polyfill的基本原理是探測是否存在 window.fetch 方法,若是沒有則用XHR實現。有些瀏覽器(Chrome 45)原生支持Fetch,但響應中有中文時會亂碼。(可以使用fetch-detector和fetch-ie8)服務器

Fetch常見坑

  • Fetch請求默認不帶cookie的,須要設置fetch(url, {credentials: 'include'})
  • 服務器返回400、500錯誤碼並不會reject,只有網絡錯誤這些致使請求不能完成時,fetch纔會被reject
  • IE八、9的XHR不支持CORS跨域,雖然提供XDomainRequest,但這個東西不支持傳cookie,因此必要時仍是使用jsonp,推薦使用fetch-jsonp

fetch請求對某些錯誤http狀態不會reject

由於fetch返回promise致使碼,在某些錯誤的http狀態下如400、500等不會reject,相反會被resolve;只有網絡錯誤會致使請求不能完成時,fetch纔會被reject;因此通常會對fetch請求作一層封裝,以下:cookie

function checkStatus (response) {
    if ( response.status >= 200 && response.status < 300) {
        return response;
    }
    const error = new Error (response.statusText);
    error.response = response;
    throw error;
}
function parseJSON (response) {
    return response.json();
}
export default function request (url, options) {
    let opt = options || {};
    return fetch (url, {credentials : 'include', ...opt})
        .then (checkStatus)
        .then (parseJSON)
        .then (data => data)
        .catch (err => err)
}

fetch不支持超時timeout處理

fetch不像大多數ajax庫能夠對請求設置超時timeout,因此在fetch標準添加超時feature以前,都須要polyfill該特性。
咱們真正須要的是abort(), timeout能夠經過timeout+abort方式實現,起到真正超時丟棄當前的請求。網絡

實現fetch的timeout功能,思想就是新建立一個能夠手動控制promise狀態的實例,對於不一樣狀態來對新建立的promise進行resolve或者reject,從而達到實現timeout功能。

方法一

var oldFetchfn = fetch; // 攔截原始的fetch方法
window.fetch = function (input, opts) {
    return new Promise ( function (resolve, reject) {
        var timeoutId = setTimeout (function () {
            reject (new Error ('fetch timeout'))
        }, opts.timeout);
    oldFetchfn (input, opts).then(
        res => {
            clearTimeout (timeoutId);
            resolve (res);
        },
        err => {
            clearTimeout (timeoutId);
            reject (err);
        }
    )
    })
}

模擬XHR的abort功能:

var oldFetchfn = fetch;
window.fetch = function (input, opts) {
    return new Promise ( function (resolve, reject) {
        var abort_promise = function () {
            reject (new Error ('fetch abort'))
        };
       var p = oldFetchfn (input, opts).then (resolve, reject);
        p.abort = abort_promise;
        return p;
    })
}

方法二: 利用Promise.race方法

Promise.race方法接受一個promise實例數組參數,表示多個promise實例中任何一個最早改變狀態,那麼race方法返回的promise實例狀態就跟着改變。

var oldFetchfn = fetch; // 攔截原始的fetch方法
window.fetch = function (input, opts) {
    var fetchPromise = oldFetchfn (input, opts);
    var timeoutPromise = new Promise ( function (resolve, reject) {
        setTimeout ( () => {
            reject (new Error ('fetch timeout'))
        }, opts.timeout)
    });
    return Promise.race([fetchPromise, timeoutPromise])
}
相關文章
相關標籤/搜索