这是我参与「第五届青训营」伴学笔记创作活动的第 13 天
Fetch API
xhr对象在open()方法创建请求时,可以通过第三个参数选择异步还是同步,但fetch只能异步
Header对象
-
Header对象是所有外发请求和入站响应头部的容器
-
每个外发请求的Request实例都包含一个空的Header实例,可通过 Requset.prototy.headers 访问(可修改属性)
-
每个入站响应的Request实例也可以通过 Requset.prototy.headers 访问包含响应头部的headers对象(可修改属性)
- Headers与Map的相似之处 Http头部本质上就是序列化后的键值对
- 都有get()、set()、has()、delete()等实例方法
- 都可以使用一个可迭代对象初始化
- 都有相同的迭代器接口 keys values entries
- Headers独有的特性:
- 在初始化时,可以使用键值对形式的对象
let seed={foo:"bar"};
let h=new Headers(seed);//对象实例初始化
let m=new Map(seed);//报错,对象不可迭代
- Headers对象通过append方法支持添加多个值
let h=new Headers();
h.append("foo","foo1");
console.log(h.get("foo")); //"foo1"
h.append("foo","foo2");
console.log(h.get("foo")); //"foo1,foo2"
Requset对象
- Request对象是获取资源请求的接口
如果该Request对象的bodyUsed属性被标记已使用,那么这两种方法都不可以创建为对象的副本
- 创建Request对象
- 第一个参数:URL
- 第二个参数:init对象,省略时,使用默认值创建对象(详情见756页和)
init对象属性
{
body:指定使用请求体时请求体的内容
cache:用于控制浏览器与HTTP缓存的交互
headers:用于指定请求头部
method:用于指定HTTP请求方法 默认是GET请求方式
......
}
let r=new Request("url"); //使用默认值创建对象,请求方式为GET
//method属性请求方式为POST,其余属性为默认值
let r=new Request("url",{method:"POST"});
- 克隆Request对象
- 第一种方法:用Request对象实例作为构造函数参数 除了bodyUsed会发生变化,其余一模一样
let r1=new Request("url1");
//请求地址url相同
let r2=new Request(r1);
console.log(r2.url); //url1
//如果传入init对象,会覆盖同名值(更新init对象)
let r2=new Request(r1,{method:"POST"});
console.log(r1.method); //默认值GET
console.log(r2.method); //POST
//bodyUsed属性发生变化
console.log(r1.bodyUsed); //true,被标记为“已使用”
console.log(r2.bodyUsed); //false
- 第二种方法:clone()方法 会创建一个一模一样的对象实例
let r1 = new Request('https://foo.com', { method: 'POST', body: 'foobar' });
let r2 = r1.clone();
//请求地址url相同
console.log(r1.url); // https://foo.com/
console.log(r2.url); // https://foo.com/
//bodyUsed属性没有变化
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
如果该Request对象的bodyUsed属性被标记已使用,那么这两种方法都不可以创建为对象的副本
- 在fetch()中使用Request对象
- 在调用fetch()时,可以传入已创建的Request对象代替url地址,init对象会覆盖同名属性值
- fetch()也不能拿已被标记为“已使用”的对象作为参数发送请求
let r=new Request("httos://foo.com")
// 向 foo.com 发送 GET 请求
fetch(r);
// 向 foo.com 发送 POST 请求
fetch(r, { method: 'POST' });
//r对象被调用过一次fetch之后,会被标记为“已使用” 之后再也不能用了
//解决办法:用clone副本作为参数发起请求
let r = new Request('https://foo.com',{ method: 'POST', body: 'foobar' });
// 3 个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
Response对象
Response对象是获取资源响应的接口
- 创建Response对象
- 构造函数 没有参数就是默认值,也可以init对象初始化对象
init对象
{
headers:
status:HTTP响应的状态
statusText:HTTP响应状态的字符串
}
-
但此时的对象并不是实际http响应返回的对象,fetch()调用返回的response对象才是有用的
-
静态方法Response.redirect():接收url和重定向状态码,返回Response对象
-
静态方法Response.error():用于表示产生网络错误的Response对象(fetch()期约被拒绝)
- 克隆Response对象 clone()方法
let r1 = new Response('foobar');
let r2 = r1.clone();
//bodyUsed属性不会变化
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
//如果响应对象的bodyUsed为true(该响应体已被读取,只能被读取一次),所以不能在创建副本
r1.text().then(console.log); //foobar
r1.text().then(console.log); //报错
//解决办法:仍然是在读取前先克隆,读取副本即可
r1.clone().text().then(console.log); //foobar
r1.clone().text().then(console.log); //foobar
//伪克隆操作
let r1 = new Response('foobar');
let r2 = new Response(r1.body);
//bodyUsed属性不会变化
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
r1.text().then(console.log); //foobar
r2.text().then(console.log); //报错
Fetch基本用法
- fetch()方法是暴露在全局作用域中的,包括主页面执行线程、模块和工作线程
- 一调用该方法,浏览器就会向url发送请求
- fetch()调用传入一个参数url(可选 传入第二个参数 init对象),返回一个期约
fetch('bar.txt')
.then((response) => {
//请求完成、资源可用时,会返回一个Response对象
console.log(response);
});
// Response { type: "basic", url: ... }
- 读取响应 text()方法 也会返回一个期约,会解决为 取得资源的完整内容
fetch('bar.txt')
.then((response) => {
response.text().then((data) => {
console.log(data);
});
});
// bar.txt 的内容
//内容的结构通常是打平的:
fetch('bar.txt') 26
.then((response) => response.text())
.then((data) => console.log(data));
// bar.txt 的内容
当出现请求失败,只要服务器返回了响应,那么就成功完成了一次HTTP请求的往返,fetch()方法返回的期约都会落定为解决状态
fetch('/bar')
.then((response) => {
console.log(response.status); // 200
console.log(response.ok); // true
});
fetch('/does-not-exist')
.then((response) => {
console.log(response.status); // 404
console.log(response.ok); // false
});
// 因为服务器没有响应而导致浏览器超时,这样真正的 fetch()失败会导致期约被拒绝
fetch('/hangs-forever')
.then((response) => {
console.log(response);
},
(err) => {
console.log(err);
}
常见Fetch请求模式
let payload = JSON.stringify({foo: 'bar'});
let jsonHeaders = new Headers({'Content-Type': 'application/json'});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种 HTTP 方法 body: payload,
headers: jsonHeaders
});
let payload = 'foo=bar&baz=qux';
let paramHeaders = new Headers({
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
});
fetch('/send-me-params', {
method:'POST', //发送请求体时必须使用一种HTTP方法
body: payload, //请求体参数放在body中
headers: paramHeaders
});
//上传单个文件
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file']");
imageFormData.append('image', imageInput.files[0]);
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
//上传多个文件
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file'][multiple]");
for (let i = 0; i < imageInput.files.length; ++i) {
imageFormData.append('image', imageInput.files[i]);
}
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
onst imageElement = document.querySelector('img');
fetch('my-image.png')
.then((response) => response.blob())
//调用blob()也会返回一个期约
.then((blob) => {
imageElement.src = URL.createObjectURL(blob);
});
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
//fetch调用并没有成功往返请求,所以返回一个拒绝的期约
.catch(() => console.log('aborted!');
// 10 毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断
Beacon API
当需要在页面生命周期尽量晚的时候向服务器发送遥测或分析数据
-
通常情况通过浏览器的unload事件发送网络请求,但是unload事件处理程序中创建的任何异步请求都会被浏览器取消,所以unload中异步xhr和fetch都不能完成
-
unload中使用同步xhr强制发送请求,会导致用户体验问题(必须等待这个请求返回后才能关闭页面),也不能完成
-
所以只能使用Beacon ApI 完成
-
不仅在页面生命周期末尾使用,任何情况都可以使用
-
调用sendBeacon()后,会把请求添加到一个内部队列,浏览器会主动发送队列中的请求
-
可以保证在原始页面关闭的情况下也会发送请求
-
信标(beacon)请求会携带调用sendBeacon()时的所有相关cookie
- sendBeacon()接收两个参数:
- url和数据有效载荷参数(请求参数)
- 调用该方法,会发送一个POST请求
- 如果请求成功进入了浏览器的内部队列,返回true,否则false
// 发送 POST 请求
// URL: 'url'
// 请求负载:'{foo: "bar"}'
navigator.sendBeacon('url', '{foo: "bar"}');