>

Native网络请求封装,跨域请求兼容

- 编辑:至尊游戏网站 -

Native网络请求封装,跨域请求兼容

IE9 跨域请求兼容

2018/05/31 · JavaScript · 跨域

原文出处: CntChen   

React Native中虽然也内置了XMLHttpRequest 网络请求API(也就是俗称的ajax),但XMLHttpRequest 是一个设计粗糙的 API,不符合职责分离的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise 友好。而Fetch 的出现就是为了解决 XHR 的问题,所以ReactNative官方推荐使用Fetch API。http://blog.csdn.NET/withings/article/details/71331726

IE9 跨域请求兼容

Chrome: You will die! IE9: Not today!

1
2
Chrome:  You will die!
IE9:     Not today!

fetch请求示例如下:

背景

搭建公司官网的框架时采用了 vuejs, 使用 history router mode 来做 SEO 优化, 使用 fetch 做网络请求, fetch 用 whatwg-fetch 做 polyfill. 根据百度浏览器市场份额统计, 2017年全年 IE9 的占有率达到 9.50%, 并且 vue 框架也是兼容到 IE9, 所以项目要求兼容到 IE9.

但是 fetch polyfill 并不兼容 IE9, 这篇文章追溯问题原因并提出解决方法.

return fetch('http://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });```
Fetch API的详细介绍及使用说明请参考如下文章:
+ [React Native 网络请求官方文档](https://github.com/reactnativecn/react-native-docs-cn/blob/master/docs/0.31/network.md)

+ [深入浅出Fetch API](http://web.jobbole.com/84924/)

+ [传统 Ajax 已死,Fetch 永生](https://github.com/camsong/blog/issues/2)

+ [【翻译】这个API很“迷人”——(新的Fetch API)](https://www.w3ctech.com/topic/854?utm_source=tuicool&utm_medium=referral)

#使用Promise封装fetch请求

由于在项目中很多地方都需要用到网络请求,为了使用上的方便,使用ES6的Promise来封装fetch网络请求,代码如下:

问题: 访问拒绝

在 IE9 下打开页面, 发现 fetch 请求报了Unhandled promise rejectionError: 拒绝访问:

  • IE9
    图片 1
  • IE11 开 IE9 调试模式
    图片 2

怀疑是 fetch 的兼容问题, 查看一下版本:

$npm list whatwg-fetch project └── whatwg-fetch@2.0.3

1
2
3
$npm list whatwg-fetch    
project
└── whatwg-fetch@2.0.3

查看了一下whatwg-fetch 兼容性: 只支持到 IE10. 然后看到 whatwg-fetchv0.11 可以兼容 IE9, 那就降级一下吧:

$ npm uninstall whatwg-fetch removed 1 package in 4.851s $ npm install whatwg-fetch@0.11 + whatwg-fetch@0.11.1 added 1 package in 5.96s

1
2
3
4
5
6
$ npm uninstall whatwg-fetch
removed 1 package in 4.851s
 
$ npm install whatwg-fetch@0.11
+ whatwg-fetch@0.11.1
added 1 package in 5.96s

再试一下, 发现还是一样的问题.

let common_url = ''; //服务器地址
let token = '';
/**

问题原因: IE9 XMLHttpRequest 不支持 CORS

fetch 的 polyfill 采用了 XMLHttpRequest 实现, 但是在 IE9 下面, XMLHttpRequest 是不支持跨域请求的. IE10 的 XMLHttpRequest 支持跨域, 而 IE8, IE9 需要使用 XDomainRequest 来实现跨域.

那就用 XDomainRequest 实现异步请求, 代码:

function fetchIe9(url, options = {}) => { if (window.XDomainRequest) { // // only support GET and POST method // request and response content type should be JSON // without response status code return new Promise((resolve, reject) => { const method = options.method || 'GET'; const timeout = options.timeout || 30000; let data = options.body || options.params || {}; if (data instanceof Object) { data = JSON.stringify(data); } const XDR = new XDomainRequest(); XDR.open(method, url); XDR.timeout = timeout; XDR.onload = () => { try { const json = JSON.parse(XDR.responseText); return resolve(json.data); } catch (e) { reject(e); } return reject({}); }; XDR.ontimeout = () => reject('XDomainRequest timeout'); XDR.onerror = () => reject('XDomainRequest error'); XDR.send(data); }); } else { // native fetch or polyfill fetch(XMLHttpRequest) // fetch... } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function fetchIe9(url, options = {}) => {
  if (window.XDomainRequest) {
    // https://developer.mozilla.org/en-US/docs/Web/API/XDomainRequest
    // only support GET and POST method
    // request and response content type should be JSON
    // without response status code
    return new Promise((resolve, reject) => {
      const method = options.method || 'GET';
      const timeout = options.timeout || 30000;
      let data = options.body || options.params || {};
      if (data instanceof Object) {
        data = JSON.stringify(data);
      }
 
      const XDR = new XDomainRequest();
      XDR.open(method, url);
      XDR.timeout = timeout;
      XDR.onload = () => {
        try {
          const json = JSON.parse(XDR.responseText);
          return resolve(json.data);
        } catch (e) {
          reject(e);
        }
        return reject({});
      };
      XDR.ontimeout = () => reject('XDomainRequest timeout');
      XDR.onerror = () => reject('XDomainRequest error');
      XDR.send(data);
    });
  } else {
    // native fetch or polyfill fetch(XMLHttpRequest)
    // fetch...
  }
}

需要注意的是:

  • XDomainRequest 只支持 GET 和 POST mehtod
  • XDomainRequest 不支持带 cookie
  • XDomainRequest 不能设置 responseType, 通信双方需要约定数据格式
  • XDomainRequest 的响应没有 response status code

题外话: whatwg-fetch 一直采用 XMLHttpRequest 来做 polyfill, whatwg-fetch1.0+ 不支持 IE9, 并不是因为没有采用 XDomainRequest, 而是因为 IE9 的状态码不符合 fetch 规范, 而 polyfill 的目标是 polyfill 规范, 而不是做兼容.

  • @param {string} url 接口地址
  • @param {string} method 请求方法:GET、POST,只能大写
  • @param {JSON} [params=''] body的请求参数,默认为空
  • @return 返回Promise
    */
    function fetchRequest(url, method, params = ''){
    let header = {
    "Content-Type": "application/json;charset=UTF-8",
    "accesstoken":token //用户登陆后返回的token,某些涉及用户数据的接口需要在header中加上token
    };
    console.log('request url:',url,params); //打印请求参数
    if(params == ''){ //如果网络请求中没有参数
    return new Promise(function (resolve, reject) {
    fetch(common_url + url, {
    method: method,
    headers: header
    }).then((response) => response.json())
    .then((responseData) => {
    console.log('res:',url,responseData); //网络请求成功返回的数据
    resolve(responseData);
    })
    .catch( (err) => {
    console.log('err:',url, err); //网络请求失败返回的数据
    reject(err);
    });
    });
    }else{ //如果网络请求中带有参数
    return new Promise(function (resolve, reject) {
    fetch(common_url + url, {
    method: method,
    headers: header,
    body:JSON.stringify(params) //body参数,通常需要转换成字符串后服务器才能解析
    }).then((response) => response.json())
    .then((responseData) => {
    console.log('res:',url, responseData); //网络请求成功返回的数据
    resolve(responseData);
    })
    .catch( (err) => {
    console.log('err:',url, err); //网络请求失败返回的数据
    reject(err);
    });
    });
    }
    }```

问题: 请求异常终止和挂起

写好了代码, 在 IE9 中, 网络请求非常诡异, 经常不行: 请求只持续了不到 1ms, 并且接收数据为 0B, 没有状态码; 但是在少数时候是可以成功请求并获取数据的.

  • IE9
    图片 3
  • IE11 开 E9 调试模式
    此时 IE11 的 IE9 调试模式是可以的, 看来模拟器还是模拟不到位.
    图片 4

查了好久, 终于看到一篇文章: Internet Explorer Aborting AJAX Requests : FIXED

IE timing out the request even though data is being transmitted.

主要的原因大概是 IE9 会将一个正在传输的请求 timeout 掉.

解决办法是:

  • 添加 onprogress 事件回调, 告知 IE9 这个请求是活动中的, 不要 timeout 掉.
  • 将请求的发送放到主线程之外, 保证 XDomainRequest 已经完全初始化好.

使用fetch请求,如果服务器返回的中文出现了乱码,则可以在服务器端设置如下代码解决:

最终代码

function fetchIe9(url, options = {}) => { if (window.XDomainRequest) { // // only support GET and POST method // request and response content type should be JSON // without response status code return new Promise((resolve, reject) => { const method = options.method || 'GET'; const timeout = options.timeout || 30000; let data = options.body || options.params || {}; if (data instanceof Object) { data = JSON.stringify(data); } const XDR = new XDomainRequest(); XDR.open(method, url); XDR.timeout = timeout; XDR.onload = () => { try { const json = JSON.parse(XDR.responseText); return resolve(json.data); } catch (e) { reject(e); } return reject({}); }; // fix random aborting: XDR.onprogress = () => {}; XDR.ontimeout = () => reject('XDomainRequest timeout'); XDR.onerror = () => reject('XDomainRequest error'); setTimeout(() => { XDR.send(data); }, 0); }); } else { // native fetch or polyfill fetch(XMLHttpRequest) // fetch... } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function fetchIe9(url, options = {}) => {
  if (window.XDomainRequest) {
    // https://developer.mozilla.org/en-US/docs/Web/API/XDomainRequest
    // only support GET and POST method
    // request and response content type should be JSON
    // without response status code
    return new Promise((resolve, reject) => {
      const method = options.method || 'GET';
      const timeout = options.timeout || 30000;
      let data = options.body || options.params || {};
      if (data instanceof Object) {
        data = JSON.stringify(data);
      }
 
      const XDR = new XDomainRequest();
      XDR.open(method, url);
      XDR.timeout = timeout;
      XDR.onload = () => {
        try {
          const json = JSON.parse(XDR.responseText);
          return resolve(json.data);
        } catch (e) {
          reject(e);
        }
        return reject({});
      };
      // fix random aborting: https://cypressnorth.com/programming/internet-explorer-aborting-ajax-requests-fixed/
      XDR.onprogress = () => {};
      XDR.ontimeout = () => reject('XDomainRequest timeout');
      XDR.onerror = () => reject('XDomainRequest error');
      setTimeout(() => {
        XDR.send(data);
      }, 0);
    });
  } else {
    // native fetch or polyfill fetch(XMLHttpRequest)
    // fetch...
  }
}
produces="text/html;charset=UTF-8"```
#fetchRequest使用如下:
+ GET请求:

结论

  • IE9 发起跨域请求要使用 XDomainRequest, 因为 IE9 下的 XMLHttpRequest 不支持跨域调用.
  • XDomainRequest 只支持 GET 和 POST method, 并且没有 response status code, 可以说是不完善的 HTTP 异步请求对象.
  • XDomainRequest 不支持指定 responseType, 使用时建议请求和返回数据格式约定为 JSON.
  • whatwg-fetch1.0+ 不支持 IE9, 是因为 IE9 的状态码不符合 fetch 规范, 而 polyfill 的目标是 polyfill 规范, 而不是做兼容.

fetchRequest('app/book','GET')
.then( res=>{
//请求成功
if(res.header.statusCode == 'success'){
//这里设定服务器返回的header中statusCode为success时数据返回成功

References

  • XDomainRequest

  • XMLHttpRequest

  • XDomainRequest – Restrictions, Limitations and Workarounds

  • Internet Explorer Aborting AJAX Requests : FIXED

1 赞 收藏 评论

图片 5

    }else{
        //服务器返回异常,设定服务器返回的异常信息保存在 header.msgArray[0].desc
        console.log(res.header.msgArray[0].desc);
    }
}).catch( err=>{ 
    //请求失败
})```
  • POST请求:
let params = {
    username:'admin',
    password:'123456'
}
fetchRequest('app/signin','POST',params)
    .then( res=>{
        //请求成功
        if(res.header.statusCode == 'success'){
            //这里设定服务器返回的header中statusCode为success时数据返回成功

        }else{
            //服务器返回异常,设定服务器返回的异常信息保存在 header.msgArray[0].desc 
            console.log(res.header.msgArray[0].desc);
        }
    }).catch( err=>{ 
        //请求失败
    })```
#fetch超时处理
由于原生的Fetch API 并不支持timeout属性,如果项目中需要控制fetch请求的超时时间,可以对fetch请求进一步封装实现timeout功能,代码如下:

  fetchRequest超时处理封装

/**

  • 让fetch也可以timeout

  • timeout不是请求连接超时的含义,它表示请求的response时间,包括请求的连接、服务器处理及服务器响应回来的时间

  • fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已

  • @param {Promise} fetch_promise fetch请求返回的Promise

  • @param {number} [timeout=10000] 单位:毫秒,这里设置默认超时时间为10秒

  • @return 返回Promise
    */
    function timeout_fetch(fetch_promise,timeout = 10000) {
    let timeout_fn = null;

    //这是一个可以被reject的promise
    let timeout_promise = new Promise(function(resolve, reject) {
    timeout_fn = function() {
    reject('timeout promise');
    };
    });

    //这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
    let abortable_promise = Promise.race([
    fetch_promise,
    timeout_promise
    ]);

    setTimeout(function() {
    timeout_fn();
    }, timeout);

    return abortable_promise ;
    }

let common_url = ''; //服务器地址
let token = '';
/**

  • @param {string} url 接口地址
  • @param {string} method 请求方法:GET、POST,只能大写
  • @param {JSON} [params=''] body的请求参数,默认为空
  • @return 返回Promise
    */
    function fetchRequest(url, method, params = ''){
    let header = {
    "Content-Type": "application/json;charset=UTF-8",
    "accesstoken":token //用户登陆后返回的token,某些涉及用户数据的接口需要在header中加上token
    };
    console.log('request url:',url,params); //打印请求参数
    if(params == ''){ //如果网络请求中没有参数
    return new Promise(function (resolve, reject) {
    timeout_fetch(fetch(common_url + url, {
    method: method,
    headers: header
    })).then((response) => response.json())
    .then((responseData) => {
    console.log('res:',url,responseData); //网络请求成功返回的数据
    resolve(responseData);
    })
    .catch( (err) => {
    console.log('err:',url, err); //网络请求失败返回的数据
    reject(err);
    });
    });
    }else{ //如果网络请求中带有参数
    return new Promise(function (resolve, reject) {
    timeout_fetch(fetch(common_url + url, {
    method: method,
    headers: header,
    body:JSON.stringify(params) //body参数,通常需要转换成字符串后服务器才能解析
    })).then((response) => response.json())
    .then((responseData) => {
    console.log('res:',url, responseData); //网络请求成功返回的数据
    resolve(responseData);
    })
    .catch( (err) => {
    console.log('err:',url, err); //网络请求失败返回的数据
    reject(err);
    });
    });
    }
    }
加入超时处理的fetchRequest网络请求的使用方法跟没加入超时处理一样。 对于fetch网络请求的超时处理的封装参考下面这篇文章而写:

[让fetch也可以timeout](http://imweb.io/topic/57c6ea35808fd2fb204eef63)

转载http://blog.csdn.net/sinat_17775997/article/details/72511024

本文由门户名站发布,转载请注明来源:Native网络请求封装,跨域请求兼容