一,基本用法
Ajax操作所用的XMLHttpRequest
对象,已经有十多年的历史,它的API设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。Fetch API是一种新规范,用来取代XMLHttpRequest
对象。它主要有两个特点,一是接口合理化,Ajax是将所有不同性质的接口都放在XHR对象上,而Fetch是将它们分散在几个不同的对象上,设计更合理;二是Fetch操作返回Promise
对象,避免了嵌套的回调函数。
下面的代码检查浏览器是否部署了Fetch API。
if ("fetch" in window){
// 支持
} else {
// 不支持
}
下面是一个Fetch API的简单例子。
fetch(url).then(function (response) {
return response.json();
}).then(function (jsonData) {
console.log(jsonData);
}).catch(function () {
console.log('出错了');
});
上面代码向指定的URL发出请求,得到回应后,将其转为JSON格式,输出到控制台。如果出错,则输出一条提示信息。注意,fetch
方法返回的是一个Promise对象。
二,stream数据流
Fetch API最大的特点是,除了返回Promise
对象,还有一点就是数据传送是以数据流(stream)的形式进行的。对于大文件,数据是一段一段得到的。
response.text().then(function (responseText) {
console.log(responseText);
}
上面代码中的text()
,其实就是一个数据流读取器,并使用指定格式解读。
Fetch API提供以下五个数据流读取器。
-
.text()
:返回字符串.json()
:返回一个JSON对象.formData()
:返回一个FormData
对象.blob()
:返回一个blob
对象.arrayBuffer()
:返回一个二进制数组
数据流只能读取一次,一旦读取,数据流就空了。再次读取就不会得到结果。解决方法是在读取之前,先使用.clone()
方法,复制一份一模一样的副本。
处理数据流的getStream
函数代码如下。
var progress = 0;
var contentLength = 0;
var getStream = function (reader) {
return reader.read().then(function (result) {
// 如果数据已经读取完毕,直接返回
if (result.done) {
return;
}
// 取出本段数据(二进制格式)
var chunk = result.value;
var text = '';
// 假定数据是UTF-8编码,前三字节是数据头,
// 而且每个字符占据一个字节(即都为英文字符)
for (var i = 3; i < chunk.byteLength; i++) {
text += String.fromCharCode(chunk[i]);
}
// 将本段数据追加到网页之中
document.getElementById('content').innerHTML += text;
// 计算当前进度
progress += chunk.byteLength;
console.log(((progress / contentLength) * 100) + '%');
// 递归处理下一段数据
return getStream(reader);
};
};
上面这样的数据流处理,可以提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。传统的XMLHTTPRequest
对象不支持数据流,所有的数据必须放在缓存里,等到全部拿到后,再一次性吐出来。
三,fetch()
fetch
方法的第一个参数可以是URL字符串,也可以是后文要讲到的Request
对象实例。Fetch
方法返回一个Promise
对象,并将一个response
对象传给回调函数。
response
对象有一个ok
属性,如果返回的状态码在200到299之间(即请求成功),这个属性为true
,否则为false
。因此,判断请求是否成功的代码可以写成下面这样。
fetch('./api/some.json').then(function (response) {
if (response.ok) {
response.json().then(function (data) {
console.log(data);
});
} else {
console.log('请求失败,状态码为', response.status);
}
}, function(err) {
console.log('出错:', err);
});
response
对象除了json
方法,还包含了服务器HTTP回应的元数据.
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);
});
上面代码中,response
对象有很多属性,其中的response.type
属性比较特别,表示HTTP回应的类型,它有以下三个值。
-
- basic:正常的同域请求
- cors:CORS机制下的跨域请求
- opaque:非CORS机制下的跨域请求,这时无法读取返回的数据,也无法判断是否请求成功
四,Headers
Fetch API引入三个新的对象(也是构造函数):Headers
, Request
和Response
。其中,Headers
对象用来构造/读取HTTP数据包的头信息。
var content = 'Hello World';
var headers = new Headers();
headers.append("Accept", "application/json");
headers.append("Content-Type", "text/plain");
headers.append("Content-Length", content.length.toString());
headers.append("X-Custom-Header", "ProcessThisImmediately");
Headers
对象的实例,除了使用append
方法添加属性,也可以直接通过构造函数一次性生成。
reqHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
五,Request对象
Request对象用来构造HTTP请求
var req = new Request("/index.html");
req.method // "GET"
req.url // "http://example.com/index.html"
Request对象实例有两个属性是只读的,不能手动设置。一个是referrer属性,表示请求的来源,由浏览器设置,有可能是空字符串。另一个是context属性,表示请求发出的上下文,如果是image,表示是从img标签发出,如果是worker,表示是从worker脚本发出,如果是fetch,表示是从fetch函数发出的。
Request对象的一个很有用的功能,是在其他Request实例的基础上,生成新的Request实例。
var postReq = new Request(req, {method: 'POST'});
六,Response
fetch方法返回Response对象实例,它有以下属性。
-
status
:整数值,表示状态码(比如200)- statusText:字符串,表示状态信息,默认是“OK”
- ok:布尔值,表示状态码是否在200-299的范围内
- headers:Headers对象,表示HTTP回应的头信息
- url:字符串,表示HTTP请求的网址
- type:字符串,合法的值有五个basic、cors、default、error、opaque。basic表示正常的同域请求;cors表示CORS机制的跨域请求;error表示网络出错,无法取得信息,status属性为0,headers属性为空,并且导致fetch函数返回Promise对象被拒绝;opaque表示非CORS机制的跨域请求,受到严格限制。
Response对象还有两个静态方法。
-
- Response.error() 返回一个type属性为error的Response对象实例
- Response.redirect(url, status) 返回的Response对象实例会重定向到另一个URL
fetch("https://example.com", init)
.then(function (response) {
// Check that the response is a 200
if (response.status === 200) {
alert("Content type: " + response.headers.get('Content-Type'));
}
});
七,body属性
Request对象和Response对象都有body属性,表示请求的内容。body属性可能是以下的数据类型。
-
- ArrayBuffer
- ArrayBufferView (Uint8Array等)
- Blob/File
- string
- URLSearchParams
- FormData
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
})
上面代码中,Request对象的body属性为表单数据。
Request对象和Response对象都提供以下方法,用来读取body。
-
- arrayBuffer()
- blob()
- json()
- text()
- formData()
注意,上面这些方法都只能使用一次,第二次使用就会报错,也就是说,body属性只能读取一次。Request对象和Response对象都有bodyUsed属性,返回一个布尔值,表示body是否被读取过。
var res = new Response("one time use");
console.log(res.bodyUsed); // false
res.text().then(function(v) {
console.log(res.bodyUsed); // true
});
console.log(res.bodyUsed); // true
res.text().catch(function(e) {
console.log("Tried to read already consumed Response");
});
上面代码中,第二次通过text方法读取Response对象实例的body时,就会报错。
这是因为body属性是一个stream对象,数据只能单向传送一次。这样的设计是为了允许JavaScript处理视频、音频这样的大型文件。
如果希望多次使用body属性,可以使用Response对象和Request对象的clone方法。它必须在body还没有读取前调用,返回一个新的body,也就是说,需要使用几次body,就要调用几次clone方法。
addEventListener('fetch', function(evt) {
var sheep = new Response("Dolly");
console.log(sheep.bodyUsed); // false
var clone = sheep.clone();
console.log(clone.bodyUsed); // false
clone.text();
console.log(sheep.bodyUsed); // false
console.log(clone.bodyUsed); // true
evt.respondWith(cache.add(sheep.clone()).then(function(e) {
return sheep;
});
});