JavaScript 通过XMLHttpRequest(XHR)来执行异步请求,这个方式已经存在了很长一段时间。虽说它很有用,但它不是最佳API。
它在设计上不符合职责分离原则,将输入、输出和用事件来跟踪的状态混杂在一个对象里。
而且,基于事件的模型与最近JavaScript流行的Promise以及基于生成器的异步编程模型不太搭(事件模型在处理异步上有点过时了)。
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
console.log(this.responseText);
}
};
xhttp.open("GET", "/", true);
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhttp.send();
而新的 Fetch API打算修正上面提到的那些缺陷。
Fetch API 提供了能够用于操作一部分 HTTP 的 JavaScript 接口,比如 requests 和 responses。它同时也提供了一个全局的 fetch() 方法——能够简单的异步的获取资源。
Fetch 提供了对 Request 和 Response 等对象通用的定义。
发送请求或者获取资源,需要使用 fetch() 方法。
Fetch API 中的GlobalFetch 接口包含了GlobalFetch.fetch() 方法,它被用于发送请求获取资源。这个方法返回一个promise,这个promise会在请求响应后被resolve,并传回Response对象。
当遇到网络错误时,fetch返回的promise会被reject,并传回TypeError。
// url (必须), options (可选)
fetch('/some/url', {
method: 'get'
}).then(function(response) {
}).catch(function(err) {
// 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(
});
fetch API 也使用了 JavaScript Promises 来处理结果/回调:
// 链式处理,将异步变为类似单线程的写法: 高级用法.
fetch('/some/url').then(function(response) {
return //... 执行成功, 第1步...
}).then(function(returnedValue) {
// ... 执行成功, 第2步...
}).catch(function(err) {
// 中途任何地方出错...在此处理 :(
});
Fetch API 的Headers类允许你去对HTTP request和response headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。
一个Headers类里包含一个header列表, 它的初始值为空或者是零个或多个键值对。
下面是一个示例:
// 创建一个空的 Headers 对象,注意是Headers,不是Header
var headers = new Headers();
// 添加(append)请求头信息
headers.append('Content-Type', 'text/plain');
headers.append('X-My-Custom-Header', 'CustomValue');
// 判断(has), 获取(get), 以及修改(set)请求头的值
headers.has('Content-Type'); // true
headers.get('Content-Type'); // "text/plain"
headers.set('Content-Type', 'application/json');
// 删除某条请求头信息(a header)
headers.delete('X-My-Custom-Header');
// 创建对象时设置初始化信息
var headers = new Headers({
'Content-Type': 'text/plain',
'X-My-Custom-Header': 'CustomValue'
});
由于Headers可以在request请求中被发送或者在response请求中被接收,并且规定了哪些参数是可写的,Headers对象有一个特殊的guard属性。这个属性没有暴露给Web,但是它影响到哪些内容可以在Headers对象中被改变。
一般来说,需要创建一个 Request 对象来包装请求头:
var request = new Request('/some-url', {
headers: new Headers({
'Content-Type': 'text/plain'
})
});
fetch(request).then(function() { /* handle response */ });
Request 对象表示一次 fetch 调用的请求信息。
可以使用构造函数Request.Request()来创建一个新的Request对象。
var req = new Request("/index.html");
console.log(req.method); // "GET"
console.log(req.url); // "http://example.com/index.html"
你也可以将一个建好的Request对象传给构造函数,这样将复制出一个新的Request:
var copy = new Request(req);
console.log(copy.method); // "GET"
console.log(copy.url); // "http://example.com/index.html"
URL以外的其他属性的初始值能够通过第二个参数传给Request构造函数。这个参数是一个json:
var uploadReq = new Request("/uploadImage", {
method: "POST",
headers: {
"Content-Type": "image/png",
},
body: "image data"
});
Request对象的mode属性用来决定是否允许跨域请求,以及哪些response属性可读。可选的mode属性值为same-origin,no-cors(默认)以及cors。
Request对象的credentials枚举属性决定了cookies是否能跨域得到。
Fetch API 的Response接口呈现了对一次请求的响应数据。
你可以使用Response.Response() 构造函数来创建一个Response对象,但一般更可能遇到的情况是,其他的API操作返回了一个Response对象,例如一个简单的 GlobalFetch.fetch()。因此Response实例通常在fetch()的回调中获得。
Response有一系列的属性:
无论Request还是Response都可能带着body。我们可以对其中的body进行处理。
Request和Response都为他们的body提供了以下方法,这些方法都读取 Response对象并且将它设置为已读(因为Responses对象被设置为了 stream 的方式,所以它们只能被读取一次),并且都返回一个Promise对象。
text():返回一个被解析为USVString格式的promise对象
处理json响应
fetch('https://davidwalsh.name/demo/arsenal.json').then(function(response) {
// 转换为 JSON
return response.json();
}).then(function(j) {
// 现在, `j` 是一个 JavaScript object
console.log(j);
});
fetch('/next/page')
.then(function(response){
return response.text();
})
.then(function(text){
// <!DOCTYPE ....
console.log(text);
})
fetch('flowers.jpg')
.then(function(response) {
return response.blob();
})
.then(function(imageBlob) {
document.querySelector('img').src = URL.createObjectURL(imageBlob);
});
fetch('/submit-json',{
method:'post',
body:JSON.stringify({
email:document.getElementById('email').value,
answer:document.getElementById('answer').value
})
})
另外,非常重要的一点要说明,那就是Request和Response的body只能被读取一次!它们有一个属性叫bodyUsed,读取一次之后设置为true,就不能再读取了。
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");
});
但有时候,我们希望多次访问body,API提供了一个clone()方法。调用这个方法可以得到一个克隆对象。不过要记得,clone()必须要在读取之前调用,也就是先clone()再读取。
var image1 = document.querySelector('.img1');
var image2 = document.querySelector('.img2');
var myRequest = new Request('flowers.jpg');
fetch(myRequest).then(function(response) {
var response2 = response.clone();
response.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
image1.src = objectURL;
});
response2.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
image2.src = objectURL;
});
});
Fetch 的支持目前还处于早期的阶段,不过正在取得良好的进展。它目前在 Firefox 39 以上,和 Chrome 42 以上都被支持了。
如果你现在就想使用它,还可以用 ,用于支持那些还未支持 Fetch 的浏览器。
Fetch API 的支持情况,可以通过检测 Headers、Request、Response 或 fetch() 是否在 Window 或 Worker 域中。
if(self.fetch) {
// run my fetch request here
} else {
// do something with XMLHttpRequest?
}
var myImage = document.querySelector("img");
fetch("flowers.jpg").then(function(response){
return response.blob();
})
.then(function(myBlob){
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
})
fetch()接收第二个可选参数,一个可以控制不同配置的init对象。
var myHeaders = new Hearders();
vart myInit = {
method:'get',
headers:myHeaders,
mode:'cors',
cache:'default'
};
fetch('flowers,jpg',myInit)
.then(function(response){
return response.blob();
})
.then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
如果遇到网络故障,fetch() promise 将会 reject,带上一个 TypeError 对象。
想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是不是为 true。
fetch('flowers.jpg').then(function(response) {
if(response.ok) {
response.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
} else {
console.log('Network response was not ok.');
}
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
});
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
var myRequest = new Request('flowers.jpg',myInit);
fetch(myRequest,myInit)
.then(function(response) {
return response.blob();
})
.then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
});
Request() 和 fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:
var anotherRequest = new Request(myRequest,myInit);
这个很有用,因为 request 和 response bodies 只能被使用一次(因为设计成了 stream 的方式,所以它们只能被读取一次)。创建一个拷贝就可以再次使用 request/response 了。
在我们自己的项目里,现在都是应用ajax的:
function ajaxNode(page){
$.get("nodeAdmin/ajaxNode",
{
page:page-1,
},function(data){
$("#nodeList").html(data);
})
}
试着更换成fetch:
function ajaxNode(page){
fetch("nodeAdmin/ajaxNode",{page:page-1}).then(function(response){
return response.text();
}).then(function(data){
$("#nodeList").html(data);
})
}
it works!而且确实发现Promise这种链式操作非常清晰。
由于 Fetch 是典型的异步场景,所以大部分遇到的问题不是 Fetch 的,其实是 Promise 的。