您的位置:首页 > 理论基础 > 计算机网络

fetch和XMLHttpRequest讲解

2017-03-30 10:55 225 查看


写在前面

fetch
 同 
XMLHttpRequest
 非常类似,都是用来做网络请求。但是同复杂的
XMLHttpRequest
的API相比,
fetch
使用了
Promise
,这让它使用起来更加简洁,从而避免陷入”回调地狱”。


两者比较

比如,如果我们想要实现这样一个需求:请求一个URL地址,获取响应数据并将数据转换成JSON格式。使用
fetch
XMLHttpRequest
实现的方式是不同的。


使用
XMLHttpRequest
实现

使用
XMLHttpRequest
来实现改功能需要设置两个监听函数,分别用来处理成功和失败的情况,同时还需要依次调用
open()
send()
方法才能实现请求。
1234567891011121314
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
实现

使用
fetch
来实现是这样的:
123456789101112131415
fetch('./api/some.json')	.then(function(res) {		if (res.status !== 200) {			console.log('Looks like there was a problem. Status Code: ' + res.status);			return;		}		// 处理响应中的文本信息		res.json().then(function(data) {			console.log(data);		});	})	.catch(function(err) {		console.log('Fetch Error : %S', err);	})
在将响应的文本信息转换成JSON格式前,需要先确保响应的状态码为200。
fetch()
请求后返回的响应是一个
stream
对象,这就意味着我们在调用
json()
方法后会返回一个
Promise
,因为读取
stream
的过程是异步操作的。


响应中的元数据

在上面的例子中,我们可以查看响应对象的状态码,也知道了如何将响应转换成JSON格式的数据。但其实我们可以访问的元数据还有以下这些:
123456789
fetch("users.json").then(function(res) {	console.log(res.headers.get('Content-Type'));	console.log(res.headers.get('Data'));	console.log(res.status);	console.log(res.statusText);	console.log(res.type);	console.log(res.url);})


响应类型

当我们发送
fetch
请求时,返回的
res.type
可能是
basic
cors
opaque
中的一种。这些类型可以告知我们资源从何而来,这样就能知道该如何处理响应对象。

当我们请求的是同一域下的资源时,响应返回的类型为
basic
,此时没有任何限制,我们可以查看响应中的任何数据。

如何请求的是跨域资源,那么会返回一个
CORS
类型的头部,并且响应类型为
cors
。这种类型跟上面的
basic
非常相似,只是它对响应头部的字段访问有限制,你只可以访问这些属性:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma

Opaque
 类型的响应也是访问跨域资源的时候产生的,只是响应头不是CORS类型的响应头而已。如果是这种类型的响应,那么我们就不能读取返回的数据,也不能查看请求的状态码,这就是意味着我们将无法确定请求是成功了还是失败了。目前在
fetch()
的实现中,无法请求跨域的资源。

我们可以为
fetch
请求定义
mode
属性,来保证只有符合条件的请求才会被处理。可以设置的
mode
属性值如下:
same-origin
 只有请求相同域下的资源才能成功,其他请求均被拒绝。
cors
 允许请求同域或者跨域资源,但是跨域必须返回相应的跨域请求头部。
cors-with-forced-preflight
 在发出实际请求前先做
preflight
检查。
no-cors
 针对跨域资源做请求,但是不返回CORS的响应头,这是属于
opaque
类型的响应(window下无法使用)

在使用
mode
时,需要将
fetch
请求的第二个参数作为配置对象,并在其中配置具体的模式,如下代码:
12345678910
fetch("http://some-site.com/cors-enabled/some.json",{mode: 'cors'})	.then(function(res){		return res.text();	})	.then(function(text) {		console.log('Request successfully', text);	})	.catch(function(err) {		console.log('Request failed', error)	})


Promise 方法链

Promise
 的特性之一就是可以实现链式调用,
fetch
也可以使用该特性,同时,使用链式调用可以让请求的处理逻辑更加通用。

如果使用接口反馈的
JSON
格式数据,那么针对每次响应,我们都需要检查响应状态并做
JSON
格式转换。其实还能简化代码,那就是把状态监测和
JSON
转换的代码放到单独的函数中去。比如:
123456789101112131415161718192021
function status(res) {	if (res.status >= 200 && res.status < 300) {		return Promise.resolve(response)	} else {		return Promise.reject(new Error(res.statusText))	}}function json(res) {	return res.json()}fetch('user.json')	.then(status)	.then(json)	.then(function(data) {		console.log('Request succeeded with JSON response', data);	})	.catch(function(err) {		console.log('Request failed', error);	})
在上面的代码中,我们定义了一个
status
方法来检查
response.status
 的状态,根据结果不同返回
Promise.resolve()
 或 
Promise.reject()
 ,这是
fetch()
方法链中的第一个方法调用。如果返回
resolve
状态,我们会继续调用
json()
,从而返回
response.json()
的执行结果。经过这些处理完我们已经可以获取解析后的
JSON
对象。如果解析失败,
Promise
返回
reject
状态,执行
catch
里的代码进行错误处理。

这么编码更大的好处在于,你可以在所有的fetch请求中使用上面的逻辑代码,从而让代码变得更加容易阅读、维护和测试。


使用
fetch
 请求发送凭证信息

如果我们想在
fetch
请求中带一些凭证信息,比如
cookie
等,我们应该将请求中的
credentials
设置为
include

123
fetch(url, {	credentials: 'include'})
注意: 服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。


Response
相关属性及方法


bodyUsed

标记返回值是否被使用过
这样设计的目的是为了之后兼容基于流的API,让应用一次消费data,这样就允许了JavaScript处理大文件例如视频,并且可以支持实时压缩和编辑。
12345678910
fetch('/test/content.json').then(function(res){    console.log(res.bodyUsed); // false    var data = res.json();    console.log(res.bodyUsed); //true    return data;}).then(function(data){    console.log(data);}).catch(function(error){    console.log(error);});


headers

返回Headers对象,该对象实现了Iterator,可通过for…of遍历
123456789101112131415161718192021
fetch('/test/content.json').then(function(res){    var headers = res.headers;    console.log(headers.get('Content-Type')); // application/json    console.log(headers.has('Content-Type')); // true    console.log(headers.getAll('Content-Type')); // ["application/json"]    for(let key of headers.keys()){        console.log(key); // datelast-modified server accept-ranges etag content-length content-type    }    for(let value of headers.values()){        console.log(value);    }    headers.forEach(function(value, key, arr){        console.log(value); // 对应values()的返回值        console.log(key); // 对应keys()的返回值    });    return res.json();}).then(function(data){    console.log(data);}).catch(function(error){    console.log(error);});


ok

是否正常返回
代表状态码在200-299之间


status

状态码( 200表示 成功)


statusText

状态描述 (‘OK’表示 成功)


type

basic
:正常的,同域的请求,包含所有的
headers
。排除
Set-Cookie
Set-Cookie2

cors
Response
从一个合法的跨域请求获得,一部分
header
body
可读。
error
:网络错误。
Response
status
是0,
Headers
是空的并且不可写。当
Response
是从
Response.error()
中得到时,就是这种类型。
opaque
: 
Response
"no-cors"
请求了跨域资源。依靠
Server
端来做限制。


url

返回完整的url字符串。如:’http://xxx.com/xx?a=1


arrayBuffer()

返回ArrayBuffer类型的数据的Promise对象


blob()

返回Blob类型的数据的Promise对象


clone()

生成一个
Response
的克隆
body
只能被读取一次,但
clone
方法就可以得到
body
的一个备份
克隆体仍然具有
bodyUsed
属性,如果被使用过一次,依然会失效
1234567891011
fetch('/test/content.json').then(function(data){    var d = data.clone();    d.text().then(function(text){        console.log(JSON.parse(text));    });    return data.json();}).then(function(data){    console.log(data);}).catch(function(error){    console.log(error);});


json()

返回JSON类型的数据的Promise对象


text()

返回Text类型的数据的Promise对象


formData()

返回FormData类型的数据的Promise对象


fetch基本用法


get
方式

1234567
fetch('/test/content.json').then(function(data){    return data.json();}).then(function(data){    console.log(data);}).catch(function(error){    console.log(error);});


post
方式

123456789101112131415
fetch('/test/content.json', { // url: fetch事实标准中可以通过Request相关api进行设置    method: 'POST',    mode: 'same-origin', // same-origin|no-cors(默认)|cors    credentials: 'include', // omit(默认,不带cookie)|same-origin(同源带cookie)|include(总是带cookie)    headers: { // headers: fetch事实标准中可以通过Header相关api进行设置        'Content-Type': 'application/x-www-form-urlencoded' // default: 'application/json'    },    body: 'a=1&b=2' // body: fetch事实标准中可以通过Body相关api进行设置}).then(function(res){ res: fetch事实标准中可以通过Response相关api进行设置    return res.json();}).then(function(data){    console.log(data);}).catch(function(error){});


fetch
不足之处


无法监控读取进度和中断请求

Promises
缺少了一些重要的
XMLHttpRequest
的使用场景。例如,
使用标准的
ES6 Promise
你无法收集进入信息或中断请求。而
Fetch
的狂热开发者更是试图提供
Promise
API
的扩展用于取消一个
Promise

这个提议有点自挖墙角的意思,因为将这将让
Promise
变得不符合标准。但这个提议或许会导致未来出现一个可取消的
Promise
标准。
但另一方面,使用
XMLHttpRequest
你可以模拟进度(监听
progress
事件),也可以取消请求(使用
abort()
方法)。
但是,如果有必要你也可以使用
Promise
来包裹它


浏览器支持

目前Chrome 42+, Opera 29+, 和Firefox 39+都支持Fetch。微软也考虑在未来的版本中支持Fetch。 讽刺的是,当IE浏览器终于微响应实现了progress事件的时候,XMLHttpRequest也走到了尽头。 目前,如果你需要支持IE的话,你需要使用一个polyfill库。



@参考 深入浅出Fetch API
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: