为什么要使用fetch?
- 传统的 Ajax请求使用的是XMLHttpRequest,XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱
- XMLHttpRequest采用的是传统的事件驱动模式,无法适配新的 Promise Api
而Fetch 的出现就是为了解决 XHR 的问题
Fetch Api有什么特点?
- 首先要理解的一点是,Fetch并非取代AJAX,而是对AJAX传统API的改进。
- 精细的功能分割:头部信息,请求信息,响应信息等均分布到不同的对象,更利于处理各种复杂的AJAX场景
- 使用Promise Api,更利于异步代码的书写。
- Fetch Api 属于HTML5新增的web api,并不属于ES官方
如何使用Fetch?
Fetch的使用很简单,调用fetch函数即可立即向服务器发送网络请求,该函数有两个参数:
- 请求地址(必填,字符串)
- 请求配置对象(选填,对象)
请求配置对象
- method: 请求方法【字符串】,默认GET
- headers: 请求头信息【对象】
- body: 请求体的内容,必须匹配请求头中的 Content-Type
- mode: 请求模式【字符串】
- cors: 默认值。配置为该值,会在请求头中加入origin和referer
- no-cors: 配置为该值,不会再请求头中加入origin和referer
- same-origin: 请求必须在同一个域中发生,如果请求其他域,则会报错
- credentails: 如何携带凭据(cookie)
- omit: 默认值,不携带cookie
- same-origin: 请求同源地址时携带cookie
- include: 请求任何地址都携带cookie
- cache: 配置缓存模式
- default: 表示fetch请求之前都将检查下http的缓存
- no-store: 表示fetch请求将完全忽略http缓存的存在,这意味着请求之前将不在检查http的缓存,拿到缓存后,也会不跟新http缓存
- no-cache: 如果存在缓存,那么fetch将发送一个条件查询request和一个正常的request,拿到响应后,它会更新http缓存
- reload: 表示fetch请求之前将忽略http缓存的存在,但是请求拿到响应后,它将主动更新http缓存。
- force-cache: 表示fetch请求不顾一切的依赖缓存,即使缓存过期了,它依然从缓存中读取,除非没有任何缓存,那么它将发送一个正常的request
- only-if-cached: 表示fetch请求不顾一切的依赖缓存,即使缓存过期了,它依然从缓存中读取,如果没有缓存,它将抛出网络错误(该设置只在mode为"same-origin"时有效)
返回值
fetch函数返回一个Promise对象
- 当收到服务器的返回结果后,Promise进入resolved状态,状态数据为Response对象
- 当网络发生错误(或其他带至无法完成交互的错误)时,Promise进入rejected状态,状态数据为错误信息
Response对象
- ok: boolean,当响应消息码在200-299之间时为true,其他为false
- status: number,响应的状态码
- text(): 用于处理文本格式的Ajax响应,它从响应中获取文本流,将其读完,然后返回一个被解决为String对象的Promise
- blob(): 用于处理二进制文件格式(比如图片或电子表格)的Ajax响应。它读取文件的原始数据,一旦读取完整个文件,就返回一个被解决为blob对象的Promise
- json(): 用于处理JSON格式的Ajax的响应。它将JSON数据流转换为一个被解决为JavaScript对象的Promise
- redirect(): 可以用于重定向要另一个URL,比如服务器返回的是一个地址,则可以利用该方法直接请求该地址,它会创建一个新的Promise,以解决来自重定向的URL的响应。
例子:
<button>得到所有的省份数据</button>
<script>
async function getProvince(){
const url = 'http://101.132.72.36:5100/api/local'
try{
const resData = await fetch(url); //得到一个response对象
const result = await resData.json(); //得到以JSON格式返回的数据
console.log(resData)
}catch(err){
console.log(err)
}
}
document.querySelector("button").onclick = function(){
getProvince();
}
</script>
Request对象
除了使用基本的fetch方法,还可以通过创建一个Request对象来完成请求(实际上,每次请求,Fetch的内部都会帮你创建一个Request对象)
语法:new Request(url地址,响应配置对象)
例子:
<button>得到所有的省份数据</button>
<script>
let req;
function getRequestInfo(){
if(!req){
const url = 'http://101.132.72.36:5100/api/local';
req = new Request(url,{})
console.log(req);
}
return req.clone();//克隆一个全新的Request对象,配置一致
}
async function getProvinces(){
try{
const resData = await fetch(getRequestInfo());
const result = await resData.json();
console.log(resData);
console.log(result);
}catch(err){
console.log(err)
}
}
document.querySelector("button").onclick = function() {
getProvinces();
}
</script>
注意点:尽量每次请求都是一个新的Requestd对象,因为如果请求需要发送大量数据的时候,比如post请求,数据需要放到请求体body中,如果发送的数据较多的时候,会形成一个流,流会记录上传的进度,如果重用该对象的话,会把上传的进度也给重用了,如果该Request对象数据量已经上传完了,进度是100%,如果再重用该request对象的话,会把进度也给重用了,这样会导致数据还没上传就已经结束了, 因此为了避免这种情况,Request也提供一种方法,可以调用Request构造器原型上clone()方法;该方法会克隆一个全新的Request对象,配置一致,但流传输是从最开始的位置开始的。
Response对象
response对象我们在上面已经提到过了,是调用fetch得到的promise在resolved的状态数据,不过,有时我们并不想去请求服务器,而是想模拟一个返回数据,这时,我们也可以利用Responsed构造器模拟出一个response对象返回
语法:new Response(响应体数据,配置对象(response对象里的属性))
例子
<button>得到模拟的response对象</button>
<script>
async function GetMockRes(){
const res = new Response(`[
{"id":1, "name":"北京"},
{"id":2, "name":"天津"}
]`, {
ok: true,
status: 200
})
const resData = await res.json();
console.log(resData);
}
document.querySelector("button").onclick = function() {
GetMockRes();
}
</script>
Headers对象
无论是request还是response对象,内部都有一个headers对象,该request和response对象内部,会将传递的请求头信息,转换为Headers
语法:new Headers({请求头信息})
Headers对象中的方法:
- has(key): 检查请求头中是否存在指定的key值
- get(key): 得到请求头中对应的key值
- set(key,value): 修改对应的键值对,如果没有对应的键,则新增
- append(key,value): 添加对应的键值对,如果有对应的键,则把值加入
- keys(): 得到所有的请求头键的集合
- values(): 得到所有的请求头中值的集合
- entries(): 得到所有请求头中键值对的集合
例子:
const heads = new Headers({
a:1,
b:2,
'Content-Type':'application/json',
c:3
})
console.log(heads.has('a')); //true
console.log(heads.get('a')) //1
heads.set('a','11'); //a变成11
heads.set('name','xiaohon'); //新增属性name,值为huang
heads.append('sex','male') //新增属性sex,值为male
heads.append('sex','female') //sex属性添加female值(male,female)
heads.keys(); //得到的是一个iterator
// function printKeys(heads){
// const keys = heads.keys(); //得到的是一个iterator
// for (const key of keys) {
// console.log(`key: ${key[0]}`)
// }
// }
// printKeys(heads)
// function printValues(heads){
// const values = heads.values();
// for (const value of values) {
// console.log(`value: ${value[1]}`);
// }
// }
// printValues(heads)
function printHeaders(heads) {
const datas = heads.entries();
for (const pair of datas) {
console.log(`key: ${pair[0]},value: ${pair[1]}`);
}
}
printHeaders(heads)
最后
用fetch api实现文件上传
流程:
- 客户端将文件数据发送给服务器 (我们需要做的只是这个)
- 服务器保存上传的文件数据到服务器端
- 服务器响应给客户的一个文件访问地址
测试地址:http://101.132.72.36:5100/api/upload 键的名称(表单域名称):imagefile
请求方法:post 请求的表单格式:multipart/form-data 请求体中必须包含一个键值对,键的名称是服务器要求的名称,值是文件数据
html5中,JS仍然无法随意的获取文件数据,但是可以获取到input元素中,被用户选中的文件数据 因此我们可以利用HTML5提供的formData构造函数来创建请求体
<input type="file" id="avatar">
<img src="" alt="" id="imgAvatar">
<button id="btn">上传</button>
<script>
async function upload(){
const avatar = document.getElementById('avatar');
if (avatar.files.length === 0) {
alert('清选择要上传的图片');
return;
}
console.log(avatar.files);
//构建请求体
const formData = new FormData;
formData.append("imagefile", avatar.files[0]); //请求体是键值对的形式
const url = "http://101.132.72.36:5100/api/upload"
const res = await fetch(url,{
method: 'POST',
body: formData
})
const result = await res.json();
console.log(result);
return result;
}
document.querySelector('button').onclick = async function(){
const result = await upload();
const img = document.getElementById("imgAvatar")
img.src = result.path;
}
</script>