网络请求资源6 | 青训营笔记

99 阅读6分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 13 天

Fetch API

xhr对象在open()方法创建请求时,可以通过第三个参数选择异步还是同步,但fetch只能异步

Header对象

  • Header对象是所有外发请求和入站响应头部的容器

  • 每个外发请求的Request实例都包含一个空的Header实例,可通过 Requset.prototy.headers 访问(可修改属性)

  • 每个入站响应的Request实例也可以通过 Requset.prototy.headers 访问包含响应头部的headers对象(可修改属性)

  1. Headers与Map的相似之处 Http头部本质上就是序列化后的键值对
  • 都有get()、set()、has()、delete()等实例方法
  • 都可以使用一个可迭代对象初始化
  • 都有相同的迭代器接口 keys values entries
  1. 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属性被标记已使用,那么这两种方法都不可以创建为对象的副本

  1. 创建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"});
  1. 克隆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属性被标记已使用,那么这两种方法都不可以创建为对象的副本

  1. 在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对象是获取资源响应的接口

  1. 创建Response对象
  • 构造函数 没有参数就是默认值,也可以init对象初始化对象
init对象
{
  headers:
  status:HTTP响应的状态
  statusText:HTTP响应状态的字符串
}
  • 但此时的对象并不是实际http响应返回的对象,fetch()调用返回的response对象才是有用的

  • 静态方法Response.redirect():接收url和重定向状态码,返回Response对象

  • 静态方法Response.error():用于表示产生网络错误的Response对象(fetch()期约被拒绝)

  1. 克隆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发送请求
  1. fetch()调用传入一个参数url(可选 传入第二个参数 init对象),返回一个期约
fetch('bar.txt')
  .then((response) => {
  //请求完成、资源可用时,会返回一个Response对象
      console.log(response);
   });
     
// Response { type: "basic", url: ... }
  1. 读取响应 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

当需要在页面生命周期尽量晚的时候向服务器发送遥测或分析数据

  1. 通常情况通过浏览器的unload事件发送网络请求,但是unload事件处理程序中创建的任何异步请求都会被浏览器取消,所以unload中异步xhr和fetch都不能完成

  2. unload中使用同步xhr强制发送请求,会导致用户体验问题(必须等待这个请求返回后才能关闭页面),也不能完成

  3. 所以只能使用Beacon ApI 完成

  • 不仅在页面生命周期末尾使用,任何情况都可以使用

  • 调用sendBeacon()后,会把请求添加到一个内部队列,浏览器会主动发送队列中的请求

  • 可以保证在原始页面关闭的情况下也会发送请求

  • 信标(beacon)请求会携带调用sendBeacon()时的所有相关cookie

  1. sendBeacon()接收两个参数:
  • url和数据有效载荷参数(请求参数)
  • 调用该方法,会发送一个POST请求
  • 如果请求成功进入了浏览器的内部队列,返回true,否则false
// 发送 POST 请求 
// URL: 'url'
// 请求负载:'{foo: "bar"}'

navigator.sendBeacon('url', '{foo: "bar"}');