JavaScript 通过XMLHttpRequest(XHR)来执行异步请求,设计上不符合职责分离原则,将输入、输出和用事件来跟踪的状态混杂在一个对象里。而且,基于事件的模型与最近JavaScript流行的Promise以及基于生成器的异步编程模型不太搭。新的 Fetch API打算修正上面提到的那些缺陷。
接着上一篇 重拾Ajax,继续一探异步请求的接班人Fetch API…
概念和用法
Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象通用的定义。它之后能够被使用到很多场景中:service workers、Cache API、其他处理请求和响应的方式,甚至任何需要生成自己的响应的方式。参考资料WHATWG Fetch 规范 和 MDN FetchAPI。
Fetch 是一个很先进的概念,类似于 XMLHttpRequest。它提供了很多 XMLHttpRequest 拥有的功能,不过它被设计成具有更强的可扩展性和更高效。
Chrome, Opera, Firefox 和 Android 浏览器的最新版本都支持Fetch API。 对于不支持的浏览器,你可以借助于fetch-polyfill来提供辅助实现。
fetch() 方法用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。
认识fetch之前,先来介绍一下与fetch相关的三个对象 Request、Body、Response、Headers
请求头对象,Fetch API 的Headers类允许你去对HTTP request 和 response 的headers执行各种操作。 这些操作包括:检索, 设置, 添加和删除。 一个Headers类里包含一个header列表, 它的初始值为空或者是零个或多个键值对。你可以使用 append()方法添加, 这个类中所有的方法, 其 header的名字顺序匹配并不区分大小写。MDN
方法append() 添加一个header信息delete() 删除指定的headerentries() 返回headers对象中的所有键值对,是一个 iterator 对象get() 从Headers对象中返回指定的值getAll() 返回全部的headerhas() 检测指定的header,返回布尔值keys() 返回所有的header的键,是一个iterator对象set() 修改或添加headervalues() 返回所有的header的值 ,是一个iterator对象
可以是一个简单的多映射的名-值表
var content = "Hello World";
var reqHeaders = new Headers();
reqHeaders.append("Content-Type", "text/plain");
reqHeaders.append("Content-Length", content.length.toString());
reqHeaders.append("X-Custom-Header", "ProcessThisImmediately");
也可以是一个json对象
reqHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
Headers的内容可以被检索
myHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");
console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
myHeaders.delete("X-Custom-Header");
console.log(myHeaders.getAll("X-Custom-Header")); // [ ]
fetch(myRequest).then(function(response) {
if(response.headers.get("content-type") === "application/json") {
return response.json().then(function(json) {
// process your JSON further
});
} else {
console.log("Oops, we haven't got JSON!");
}
});
Request
FetchAPI的资源请求对象MDN
构造器创建一个实例
var req = new Request('data.json', {
method:'POST',
headers:{},
body:new FormData(document.getElementById('login-form')),
cache:'default',
})
属性
method 请求的方法POST/GET等url 请求的地址headers 请求头(可以是Headers对象,也可是JSON对象)context 请求的上下文referrer 指定请求源地址mode 请求的模式(是跨域cors还是正常请求no-cors)credentials 跨域请求时,是否携带cookie信息(omit跨域携带/same-origin同源携带)redirect 重定向integrity 一个散列值,用于检验请求资源的完整性MDNcache 是否缓存这个请求
方法
clone() 复制一个当前request对象的实例
Body
Fetch mixin 对象,提供了关联 response/request 中 body 的方法,可以定义它的文档类型以及请求如何被处理。
Request 和 Response 对象都实现了Body的接口,所以都拥有Body的方法和属性,用于指定请求体中的body或响应体的内容的数据类型(arrayBuffer/blob /json/text) 主要是做数据类型的转换。
属性
bodyUsed 用于判断是否在响应体中是否设置过body读取类型
方法
arrayBuffer() 将响应流转换为buffer数组的promise对象,并将bodyUsed状态改为已使用blob() 将响应流转换为大的二进制的promise对象,并将bodyUsed 状态改为已使用,一般用于文件读取(下载大文件或视频)formData() 将响应流转换为formData的promise对象,并将bodyUsed状态改为已使用json() 将响应流转换为json的promise对象,并将bodyUsed状态改为已使用text() 将响应流转换为文本字符串的promise对象,并将bodyUsed状态改为已使用
Response
FetchAPI的响应对象MDN
属性(只读)
type 响应的类型 basic/cors等url 包含Response的URL.useFinalURL 包含了一个布尔值来标示这是否是该Response的最终URLstatus 响应的状态码 1xx-5xxok 表示响应成功statusText 状态码的信息headers 响应头的Headers对象bodyUsed 是否设置过响应内容的类型
方法
clone() 创建一个Response对象的克隆error() 返回一个绑定了网络错误的新的Response对象redirect() 用另一个URL创建一个新的 response.
Fetch 语法 和 示例
语法
fetch('api/data.json', {
method:'POST', //请求类型GET、POST
headers:{},// 请求的头信息,形式为 Headers 对象或 ByteString。
body:{},//请求发送的数据 blob、BufferSource、FormData、URLSearchParams(get或head方法中不能包含body)
mode:'',//请求的模式,是否跨域等,如 cors、 no-cors 或者 same-origin。
credentials:'',//cookie的跨域策略,如 omit、same-origin 或者 include。
cache:'', //请求的 cache 模式: default, no-store, reload, no-cache, force-cache, or only-if-cached.
}).then(function(response) { ... });
mode
no-cors允许来自CDN的脚本、其他域的图片和其他一些跨域资源,但是首先有个前提条件,就是请求的method只能是”HEAD”,”GET”或者”POST”。此外,任何 ServiceWorkers 拦截了这些请求,它不能随意添加或者改写任何headers,除了这些。第三,JavaScript不能访问Response中的任何属性,这保证了 ServiceWorkers 不会导致任何跨域下的安全问题而隐私信息泄漏。cors通常用作跨域请求来从第三方提供的API获取数据。这个模式遵守CORS协议。只有有限的一些headers被暴露给Response对象,但是body是可读的。same-origin如果一个请求是跨域的,那么返回一个简单的error,这样确保所有的请求遵守同源策略。
cache
default缓存相同的请求no-store不缓存任何请求reload创建一个正常的请求,并用响应更新HTTP缓存no-cache如果HTTP缓存中有响应,并且不是正常请求,则Fetch创建条件请求。然后,它使用响应更新HTTP缓存。force-cacheFetch使用HTTP缓存中与请求匹配的任何响应,不管是否过期。如果没有响应,则会创建正常请求,并使用响应更新HTTP缓存。only-if-cachedFetch使用HTTP缓存中与请求匹配的任何响应,不管是否过期。如果没有响应,则返回网络错误。 (只有当请求的模式为“same-origin”时,才能使用任何缓存重定向,假设请求的重定向模式为“follow”,重定向不会违反请求的模式)。
如果header中包含名称为“If-Modified-Since”,“If-None-Match”,“If-Unmodified-Since”,“If-Match”和“If-Range”之一,如果是“default”,fetch 会将 cache 自动设置为 “no-store”。
Fetch 示例
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;
});
简单封装
前面有了ajax的封装过程,fetch的封装就更简单了,因为fetch()方法本身返回的是promise对象,那就不需要使用promise再包装了,代码也简洁很多。
class AjaxFetch{
constructor(opts, params){
const isUrl = typeof opts === 'string';
this.defaults = {
method:'GET',
headers:{},
data:{},
credentials:'include', //默认不带cookie,指定inlude,始终携带cookie
cache:'default',
// mode:''//请求时会自动识别是否跨域,不需要手动设置
};
this.options = Object.assign(this.defaults, (isUrl ? params : opts) || {});
this.methods = ['GET','PUT','PATCH','DELETE','POST'];
this.url = isUrl ? opts : this.options.url;
this.init();
return isUrl ? this : this[this.options.method.toLowerCase()](this.options.data)
}
init(){
this.methods.forEach(method=>{
this[method.toLowerCase()] = data => {
if('GET' == method){
this.url += (this.url.includes('?')?'&':'?' + this.transformData(data))
}else{
if(data instanceof FormData){
this.options.headers['Content-Type'] = 'multipart/form-data;';
}else{
this.options.headers['Content-Type'] = 'application:/x-www-form-urlencoded:charset=UTF-8';
}
this.options.body = this.transformData(data);
}
delete this.options.data;
this.options.method = method;
return fetch(this.url, this.options);
}
})
}
transformData(obj){
// 这里还需要做更多的处理
if(obj instanceof FormData) return obj;
var params = [];
for(var i in obj){
params.push(`${i}=${obj[i]}`);
}
return params.join('&');
}
}
function http(opt, pms){
if(!opt) return;
return new AjaxFetch(opt, pms);
}
使用
var api = {
news: http('api/d1.json'),
users: http('api/d2.json')
}
//get
api.news.get({uname:'xxx', pwd:123}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
//post
var frmData = new FormData(document.getElementById('frm1'));
api.news.post(frmData).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
//类似$.ajax
http({
url:'api/d1.json',
method:'POST',
headers:{'Content-Type':'multipart/form-data; boundary=----'+new Date().getTime()},
data: new FormData(document.getElementById('frm1'))
}).then(res=>{
console.log(res)
debugger;
})
结合Async
async function test2(){
let r1 = await api.news.get({'name':'aa'});
let r2 = await api.users.post(new FormData(document.getElementById('frm1')))
return [r1, r2];
}
var res = test2();
console.log(res);