Fetch

Why Fetch

XMLHttpRequest是一个设计粗糙的API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的Promise,generator/yield,async/await友好。

Fetch的出现就是为了解决XHR的问题。
传统使用XHR发送一个json请求一般是这样

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后

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捕获

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
  • IE8、9的XHR不支持CORS跨域,虽然提供XDomainRequest,但这个东西不支持传cookie,所以必要时还是使用jsonp,推荐使用fetch-jsonp

fetch请求对某些错误http状态不会reject

因为fetch返回promise导致码,在某些错误的http状态下如400、500等不会reject,相反会被resolve;只有网络错误会导致请求不能完成时,fetch才会被reject;所以一般会对fetch请求做一层封装,如下:

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])
}
posted @ 2017-08-15 16:05  douglasvegas  阅读(308)  评论(0编辑  收藏  举报