说道fetch就不得不提XMLHttpRequest了,XHR在发送web请求时需要开发者配置相关请求信息和成功后的回调,尽管开发者只关心请求成功后的业务处理,但是也要配置其他繁琐内容,导致配置和调用比较混乱,也不符合关注分离的原则;fetch的出现正是为了解决XHR存在的这些问题。例如下面代码:
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
上面这段代码让开发者只关注请求成功后的业务逻辑处理,其他的不用关心,相当简单;也比较符合现代Promise形式,比较友好。
fetch是基于Promise设计的,从上面代码也能看得出来,这就要求fetch要配合Promise一起使用。正是这种设计,fetch所带来的优点正如传统 Ajax 已死,Fetch 永生总结的一样:
不过话说回来,fetch虽然有很多优点,但是使用fetch来进行项目开发时,需要的配置,以及遇到的fetch使用的常见问题。
如果未定义,cors则假定为默认值。
cache 属性值
一个RequestCache值。可用的值是:
default:浏览器在HTTP缓存中查找匹配的请求。
no-store:浏览器从远程服务器获取资源,而不先查看缓存,并且不会使用下载的资源更新缓存。
reload:浏览器从远程服务器获取资源,而不先查看缓存,然后用下载的资源更新缓存。
no-cache :浏览器在HTTP缓存中查找匹配的请求。
force-cache:浏览器在HTTP缓存中查找匹配的请求。
only-if-cached:浏览器在HTTP缓存中查找匹配的请求。
该"only-if-cached"模式只能用于请求的mode为"same-origin"的情况。如果请求的redirect属性是"follow",并且重定向不违反"same-origin"模式,则会遵循缓存重定向。
fetch默认是不发送cookie
1.用法
该Headers接口允许您通过Headers()构造函数创建自己的headers对象。headers对象是名称到值的简单多重映射:
var content = "Hello World";
var myHeaders = new Headers();
myHeaders.append("Content-Type", "text/plain");
myHeaders.append("Content-Length", content.length.toString());
myHeaders.append("X-Custom-Header", "ProcessThisImmediately");
同样可以通过传递一个数组或一个对象字面值给构造函数来实现:
myHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": content.length.toString(),
"X-Custom-Header": "ProcessThisImmediately",
});
还可以:
headers:{
"Content-Type":"application/x-www-form-urlencoded"
}
内容可以被查询和检索:
console.log(myHeaders.has("Content-Type")); // true
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");
console.log(myHeaders.get("X-Custom-Header")); // "ProcessThisImmediately"
myHeaders.delete("X-Custom-Header");
2.配置
get不存在请求实体部分,键值对参数放置在 URL 尾部,因此请求头不需要设置 Content-Type 字段。
用于标记请求体数据的格式Content-Type
// 消息主体是序列化json字符串
形式:
{"name":"小明","password":"123456"}
controller 的入参使用@RequestBody修饰,说明是要使用json的格式接收。request.getInputStream(),request.getReader() 获取。并且getInputStream获取参数后,request.getParameter() 再不能得到参数。
// 浏览器原生的form表单,请求体中的数据会以普通表单形式(键值对)发送到后端
形式:
key1=value1&key2=value2
后端取值方式:
request.getParameter()、request.getParameterMap()
// 发送 POST 请求,参数为:aaa=aaa,bbb=你的我的啊啊啊,file=图片
POST / HTTP/1.1
Host: www.bilibili.com
Content-Type: multipart/form-data;boundary=------FormBoundary15e896376d1
Content-Length: 19532
------FormBoundary15e896376d1
Content-Disposition: form-data; name="aaa"
aaa
------FormBoundary15e896376d1
Content-Disposition: form-data; name="bbb"
你的我的啊啊啊
------FormBoundary15e896376d1
Content-Disposition: form-data; name="file"; filename="cat-icon.png"
Content-Type: image/png
[message-part-body; type:image/png, size:19201 bytes]
------FormBoundary15e896376d1--
首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。
然后 Content-Type 里指明了数据是以 mutipart/form-data 来编码,本次请求的 boundary 是什么内容。
消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 –boundary 开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。
如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 –boundary– 标示结束。
它是一种使用 HTTP作为传输协议,XML 作为编码方式的远程调用规范。
请求和响应都可能包含body数据。一个body是以下任何一种类型的实例:
Body mixin定义了以下方法来提取体(由得到的Request和Response实施)。这些都会返回一个最终解决实际内容的承诺。
var debug = {hello: "world"};
var blob = new Blob([JSON.stringify(debug, null, 2)], {type : 'application/json'});
上传JSON数据
使用fetch()开机自检JSON编码的数据。
将参数序列化json字符串进行传递
var url = 'https://example.com/profile';
var data = {username: 'example'};
fetch(url, {
method: 'POST', // or 'PUT'
body: JSON.stringify(data), //'{"name":"hehe","age":10}'
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
请求体中的数据会以普通表单形式(键值对)发送到后端
const options = {
method: "POST",
body: qs.stringify(data),// 'name=hehe&age=10'
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
// URLSearchParams,插入一个指定的键/值对作为新的搜索参数。
let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//添加第二个foo搜索参数。
params.append('foo', 4);
//查询字符串变成: 'foo=1&bar=2&foo=4'
注:
当使用application/json 的时候,body直接是 JSON.stringify(paramObject) 比较简单
当使用 ‘Content-Type’: ‘application/x-www-form-urlencoded’,时候,需要将对象转换为普通表单形式(键值对)的字符串
疑惑:body已经将数据封装成想要的格式,那fetch 请求中的请求头设置Content-Type到底改变了什么
JSON.parse(用于从一个字符串中解析出json 对象)
JSON.stringify(用于从一个对象解析出字符串)
qs.parse()// 将URL解析成对象的形式
qs.stringify()// 将对象解析成URL的形式
上传文件
可以使用 HTML
<input type="file"/>
input 元素、FormData ()将form表单元素的name与value进行组合,实现表单数据的序列化,从而减少表单元素的拼接, 和fetch()来上载文件。
var formData = new FormData();
var fileField = document.querySelector("input[type='file']");
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData,
headers: {'Content-Type': 'multipart/form-data'}}
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
当fetch() promise被解析时,Response实例被返回。
一个 Promise,resolve 时回传 Response 对象:
属性:
has(name) (boolean) - 判断是否存在该信息头
get(name) (String) - 获取信息头的数据
getAll(name) (Array) - 获取所有头部数据
set(name, value) - 设置信息头的参数
append(name, value) - 添加header的内容
delete(name) - 删除header的信息
forEach(function(value, name){ ... }, [thisContext]) - 循环读取header的信息
方法:
其他方法:
特点:fetch请求对某些错误http状态不会reject
这主要是由fetch返回promise导致的,因为fetch返回的promise在某些错误的http状态下如400、500等不会reject,相反它会被resolve;
仅仅在发生“network error(网络错误)”才会被拒绝。如果可以服务器获得http错误状态,则表明服务器正常工作且在处理请求,而“network error(网络错误)”表示根本无法到达服务器(例如连接拒绝或名称未解析)或请求配置有错误(错误的请求地址)。
// 解决
if(response.ok){
return response.json();
}
else {
return Promise.reject({
status: response.status,
statusText: response.statusText
})
}
对fetch请求做一层封装。
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {
return response.json();
}
export default function request(url, options) {
let opt = options||{};
return fetch(url, {credentials: 'include', ...opt})
.then(checkStatus)
.then(parseJSON)
.then((data) => ( data ))
.catch((err) => ( err ));
}
// 解决
npm install fetch-jsonp --save-dev
fetchJsonp('/users.jsonp', {
timeout: 3000,
jsonpCallback: 'custom_callback'
})
.then(function(response) {
return response.json()
}).catch(function(ex) {
console.log('parsing failed', ex)
})
核心是利用建立一个超时的abortPromise和接口请求的fetchPromise传入 Promise.race() 来进行处理,哪个Promise先返回结果则最终输出这个Promise的返回值。
var oldFetchfn = fetch; //拦截原始的fetch方法
window.fetch = function(input, opts){//定义新的fetch方法,封装原有的fetch方法
var fetchPromise = oldFetchfn(input, opts);
var timeoutPromise = new Promise(function(resolve, reject){
setTimeout(()=>{
reject(new Error("fetch timeout"))
}, opts.timeout)
});
retrun Promise.race([fetchPromise, timeoutPromise])
//哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
}
伴随fetch的问世及发展,目前在生产环境使用fetch的企业越来越多。开源社区上有关fetch polyfilll已经多款供选,又因其基于Promise,以下可以完美的解决其兼容性问题(特别是IE),仅参考:
fetch 的好处无需多语,但fetch也存在一些问题,例如
特别是fetch在跨域问题上与传统跨域的处理方式的区别。