0
点赞
收藏
分享

微信扫一扫

【JavaScript 详细笔记】网络请求与远程资源(XMLHttpRequest、Fetch API)

IT影子 2022-04-06 阅读 20
javascript

文章目录

【JavaScript 详细笔记】网络请求与远程资源(XMLHttpRequest、Fetch API)

2005年,Ajax(Asynchronous JavaScript+XML)技术的出现,发送服务器请求额外数据而不刷新页面。

XHR对象的API被普遍认为比较难用,而Fetch API自从诞生以后就迅速成为了XHR更现代替代标准。Fetch API支持期约promise和服务线程service worker。

一、XMLHttpRequest对象

IE5是第一个引入XHR对象的浏览器。
所有现代浏览器都通过XMLHttpResquest构造函数原生支持XHR对象。

let xhr = new XMLHttpRequest();

1、使用XHR

  1. 首先调用open(‘get’, ‘example.php’, false)方法
    接收三个参数:请求类型,请求URL,表示请求是否异步的布尔值

  2. 要发送定义好的请求,必须调用send()方法
    接收一个参数:作为请求体发送的数据,如果不需要发送请求体,则必须传null

    因为这个请求是同步的,所以JS代码会等待服务器响应之后在继续执行,收到响应后,XHR对象的以下属性会被填充上数据:

    • responseText:响应体返回的文本
    • responseXML:包含响应数据的XML DOM文档
    • status:响应的HTTP状态
    • statusText:响应的HTTP状态描述
  3. XHR对象有一个readyState属性,表示当前处在请求/响应过程的哪个阶段

    • 0:未初始化,尚未调用open方法
    • 1:已打开,已调用open方法
    • 2:已发送,已调用send方法
    • 3:接收中,已经接收到部分响应
    • 4:完成。已经接收到所有响应,可以使用了。
  4. 每次readyState从一个值变为另一个值,都会触发readystatechange事件

  5. 在收到响应之前如果想取消异步请求,可以调用abort()方法

使用XHR的详细步骤:

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { // 为了保证跨浏览器兼容,onreadystatechange需要在open之前赋值
  if(xhr.readyState == 4) { // 已经接收到所有响应
    if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
      alert(xhr.responseText);
    }else {
      alert('Request was unsuccessful:' + xhr.status);
    }
  }
};
xhr.open('get', 'example.php', true);
xhr.send(null);

2、HTTP头部

每个HTTP请求和响应都会携带一些头部字段,默认情况下,XHR请求会发送以下头部字段:

  • Accept
  • Accept-Charset
  • Accept-Encoding
  • Accept-Language
  • Connection
  • Cookie
  • Host
  • Referer
  • User-Agent

如果需要发送额外的请求头部,可以使用setRequsetHeader()方法,它接受俩参数:头部名称的名称和值。必须在open之后send之前调用它。

xhr.open('get', 'example.php', true);
xhr.setRequsetHeader('MyHeader', 'MyValue');
xhr.send(null);
  • getResponseHeader():从XHR对象获取相应头部
    let myHeader = xhr.getResponseHeader("MyHeader");
  • getAllResponseHeaders():返回包含所有响应头部的字符串
    let allHeaders = xhr.getAllResponseHeaders();

3、XMLHttpRequest Level 2

XMLHttpRequest Level 1 只是把已经存在的XHR对象的实现细节明确了一下。
XMLHttpRequest Level 2 又进一步发展了XHR对象,并非所有浏览器都实现了它的所有部分,但所有浏览器都实现了它的其中部分功能。

FormData类型

用于表单序列化

xhr.open('post', 'example.php', true);
let form = document.getElementById('user-info');
xhr.send(new FormData(form));

超时

IE8给XHR对象新增了一个timeout属性,用于表示发送请求后等待多少毫秒,如果响应不成功就中断请求。

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { // 为了保证跨浏览器兼容,onreadystatechange需要在open之前赋值
  if(xhr.readyState == 4) { // 已经接收到所有响应
    try {
      if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
        alert(xhr.responseText);
      }else {
        alert('Request was unsuccessful:' + xhr.status);
      }
    }catch (ex) {}
  }
};
xhr.open('get', 'example.php', true);
xhr.timeout = 1000;
xhr.ontimeout = function() {
  alert('Request did not return in a second.')
}
xhr.send(null);

在超时后访问status属性会发生错误,可以把检查status属性的代码封装在try、catch语句中。

overrideMimeType()方法

Firefox首先引入了overrideMimeType方法用于重写XHR响应的MIME类型。

假设服务器实际上发送了XML数据,但响应头设置的MIME类型是text/plain。结果就会导致数据是XML,但responseXML属性值是null。此时调用overrideMimeType方法可以保证将响应成XML而不是纯文本来处理

xhr.overrideMimeType('text/xml');

二、进度事件

1、load事件

Firefox在实现XHR的时候,增加了一个load事件替代readystatechange事件。

let xhr = new XMLHttpRequest();
xhr.onload = function() {
  if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
    alert(xhr.responseText);
  }else {
    alert('Request was unsuccessful:' + xhr.status);
  }
}
xhr.open('get', 'example.php', true);
xhr.send(null);

2、progress事件

Mozilla在XHR对象上创建了progress事件,在浏览器接收数据期间,这个事件会反复触发

onprogress事件处理程序会接受event对象,其target属性是XHR对象,还有三个属性:

  • lengthComputable:一个布尔值,表示进度信息是否可用
  • position:接收到的字节数
  • totalSize:是响应的Content-Length头部定义的总字节数

三、跨源资源共享

通过XHR进行Ajax通信的一个主要限制是跨源安全策略。默认情况下XHR只能访问与发起请求的页面在同一个域内的资源。

跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。

对于简单的请求(GET、POST)没有自定义头部,而且请求体是text/plain类型,这样的请求在发送时会有一个额外的头部Origin。Origin头部包含发送请求的页面的源(协议、域名、端口):
Origin: http://www.baidu.com

如果服务器决定响应请求,那么应该发送Access-Control-Allow-Origin头部,包含相同的源。或者资源是公开的,那么就包含*
Access-Control-Allow-Origin: *

如果没有这个头部,或者有但源不匹配,则表明不会响应浏览器请求。否则,服务器就会处理这个请求。(无论请求是否响应都不会包含cookie信息)

跨域XHR对象允许访问status、statusText属性,也允许同步请求。
跨域XHR对象施加的一些限制:

  • 不能使用setRequestHeader()设置自定义头部
  • 不能发送请求cookie
  • getAllResponseHeaders()方法始终返回空字符串

1、预检请求

CORS通过一种预检请求的服务器验证机制。允许使用自定义头部、除GET、POST之外的方法,以及不同请求体内容类型。
在要发送涉及上述某种高级选项的请求中,会向服务器发送一个‘预检’请求。
这个请求使用OPTIONS方法发送并包含以下头部:

  • Origin:与简单请求相同
  • Access-Control-Request-Method
  • Access-Control-Request-Headers

在这个请求发送后,服务器可以确定是否允许这种类型的请求。服务器会通过在响应中发送如下头部与浏览器沟通这些信息:

  • Access-Control-Allow-Origin:与简单请求相同
  • Access-Control-Allow-Methods:允许的方法
  • Access-Control-Allow-Headers:服务器允许的头部
  • Access-Control-Max-Age:缓存预检请求的秒数

2、凭据请求

默认请求下,跨源请求不提供凭据(cookie、HTTP认证和客户端SSL证书)

可以通过将withCredentials属性设置为true来表明请求会发送凭据,如果服务器允许带凭据的请求,那么可以在响应中包含如下HTTP头部:
Access-Control-Allow-Credentials: true

四、替代性跨域技术

1、图片探测

let img = new Image();
img.onload = img.onerror = function() {
  alert('Done!');
};
img.src = 'http://www.baidu.com/test?name="lyb"'

图片探测只能发送GET请求,无法获取服务器响应的内容。只能利用图片探测实现浏览器与服务器单向通信的原因。

2、JSONP

function handleResponse(response) {
  console.log(`you're at ip address ${response.ip}, which is in ${response.city}, ${response.region_name}`);
}

let script = document.createElement('script');
script.src = 'http://www.baidu.com/json/?callback=handleResponse';
document.body.insertBefore(script, document.body.firstChild);
  • JSONP是从不同的域拉取可执行代码。如果这个域不可信,则可能在响应中加入恶意内容。
  • 不好确认JSONP请求是否失败。

五、Fetch API

Fetch API能执行XMLHttpRequest对象的所有任务,更容易使用。XMLHttpRequest可以选择异步,但是Fetch API必须是异步

1、基本用法

  1. 分派请求
    fetch方法有一个必须的参数,多数情况下这个参数是要获取资源的URL,它返回一个期约:
    let r = fetch('/bar'); // Promise <pending>
    
    fetch('bar.txt')
      .then((response) => {
        console.log(response); // { type: 'basic', url: ... }
      })
    
  2. 读取响应
    读取响应就简单方式是取得纯文本格式的内容,用到text()方法,它返回一个期约:
    fetch('bar.txt')
      .then((response) => response.text())
      .then((data) => console.log(data)); // bar.txt的内容
    
  3. 处理状态码和请求失败
    Fetch API通过Response的status状态跟statusText状态文本属性检查响应状态。成功获取的请求通常返回200:
    fetch('/bar')
      .then((response) => {
        console.log(response.status, response.statusText)
      })
    
    • 请求不存在的资源会产生404的状态码
    • 请求的URL抛出服务器错误会产生500的状态码
    • 显示的设置fetch遇到重定向的行为(默认是跟随重定向并返回状态码不是300-399的响应),跟随重定向时,响应对象的redirected属性会设置成true,而状态码还是200
    • 服务器没有响应导致浏览器超时
      fetch('/hangs-forever')
        .then((response) => {}, (err) => {})
      
    • 违反CORS、无网络连接、HTTPS错配及其他浏览器、网络策略问题都会导致期约被拒绝,可以通过url属性检查fetch发送请求时的完整URL
      response.url
  4. 自定义选项(fetch的第二个参数~···)

2、常见的Fetch请求模式

  1. 发送JSON数据
    let payload = JSON.stringfy({
      name: 'lyb'
    });
    let jsonHeaders = new Headers({
      'Content-Type': 'application/json'
    });
    
    fetch('/send', {
      method: 'POST',
      body: payload,
      headers: jsonHeaders
    });
    
  2. 在请求体中发送参数
    let payload = 'name=lyb&age=20';
    let paramHeaders = new Headers({
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    });
    
    fetch('/send', {
      method: 'POST',
      body: payload,
      headers: paramHeaders
    });
    
  3. 发送文件(也可以发送多个文件)
    let imageFormData = new FormData();
    let imageInput = document.querySelector("input[type='file']");
    
    imageFormData.append('image', imageInput.files[0]);
    fetch('/send', {
      method: 'POST',
      body: imageFormData
    });
    
  4. 加载Blob文件
    使用响应对象上暴露的blob()方法,返回一个期约,解决为一个Blob的实例,然后传给URL.createObjectUrl()以生成可以添加给图片元素src的属性值
    const imageElement = document.querySelect('img');
    fetch('/my-img.png')
       .then(response => response.blob())
       .then(blob => imageElement.src = URL.createObjectUrl(blob));
    
  5. 发送跨源请求
    从不同的源请求资源,响应要包含CORS头部才可以保证浏览器接收到响应
    fetch('//cross-origin.com') // 报错
    
    <!-- 如果不需要访问响应,可以发送no-cors请求 ,此时响应的type属性为opaque -->
    fetch('//cross-origin.com', { method: 'no-cors' })
       .then(response => console.log(response.type)); // opaque
    
  6. 中断请求
    let abortController = new AbortController();
    
    fetch('wikipedia.zip', { signal: abortController.signal })
       .catch(() => console.log('aborted!'))
    
    setTimeout(() => abortController.abort(), 10); // 10毫秒后中断请求
    

3、Headers对象

Headers是所有外发请求(Request.prototype.headers)和入站响应头部(Response.prototype.headers)的容器。这两属性都是可修改属性,通过new Headers()也可以创建一个新实例

  1. Headers与Map相似

HTTP头部本质上是序列化后的键值对,它们的JavaScript表示则是中间接口。

  1. Headers独有的特性

初始化Headers的时候,可以使用键值对的形式的对象,而Map则不可以

let h = new Headers();
h.append('foo', 'bar');
console.log(h.get('foo')); // bar 
  1. 头部护卫

某些情况下,并非所有的HTTP头部都可以被客户端修改,而Headers对象使用护卫来防止不被允许的修改。
不同的护卫设置:

  • none
  • request
  • request-no-cors
  • response
  • immutable

4、Request对象

  1. 创建Request对象
    第一个参数:URL
    let r = new Request('https://foo.com');
    第二个参数:一个init对象
    new Request('https://foo.com', { method: 'POST' });

  2. 克隆Request对象

  • 方法一:使用Request构造函数
    传入的init会覆盖源对象中同名的值:

    let r = new Request('https://foo.com')
    let r2 = new Request(r, { method: 'POST' });
    console.log(r.method, r2.method); // GET POST
    

    使用这种方法并不能得到相同的副本,最明显的,第一个请求会标记为true:

    let r = new Request('https://foo.com', { method: 'POST', body: 'foobar'   })
    let r2 = new Request(r, { method: 'POST' });
    
    console.log(r.bodyUsed, r2.bodyUsed); // true false
    
  • 方法二:使用clone()方法
    会创建相同的副本:

    let r = new Request('https://foo.com');
    let r2 = r.clone();
    

    如果请求对象的bodyUsed属性为 true,那么使用哪种方式都不会创建这个对象的副本:

    let r = new Request('https://foo.com');
    let r2 = r.clone();
    
    r.text();
    r.clone();
    
  1. 在fetch()中使用Request对象
    fetch不能拿请求体已经用过的Request对象来发送请求:
let r = new Request('https://foo.com', {
  method: "POST",
  body: "foobar"
});

r.text();

fetch(r); // TypeError

通过fetch使用Request对象也会将请求体标记为使用

要想基于包含请求体的相同Request对象多次调用fetch(),必须在第一次发送fetch请求前调用clone():

let r = new Request('https://foo.com', {
  method: "POST",
  body: "foobar"
});

fetch(r.clone()); // 都会成功
fetch(r.clone());
fetch(r);

5、Response对象

  1. 创建Response对象
  • 通过构造函数初始化Response对象时不需要参数:
    let r = new Response();
    
  • 可接受一个可选的body参数(可以为null),可以接收一个可选的init对象,包含以下键值(headers, status, statusText)
    let r = new Response('foobar', {
      status: 418,
      statusText: 'ceshi'
    });
    console.log(r);
    
  • 大多数产生Response的主要方式是调用fetch,它返回一个最后会解决为Response对象的期约,代表实际的HTTP响应。
  • Response类还有两个用于生成Response对象的静态方法:
    Response.redirect(): 接收一个URL和一个重定向状态码(3开头)
    Response.error():用于产生表示网络错误的Response对象
  1. 克隆Response对象
  • 克隆它的主要方法是clone,会创建一个一模一样的副本,不会覆盖任何值

  • 如果响应对象的bodyUsed属性为true,则不能在创建这个对象的副本。在响应体被读取之后在克隆会导致TypeError异常

    let r = new Response('foobar');
    console.log(r);
    
    r.text().then(console.log)
    r.text().then(console.log) // TypeError
    
  • 要多次读取包含响应体的同一个Response对象,必须在第一次读取前调用clone:

    let r = new Response('foobar');
    console.log(r);
    
    r.clone().text().then(console.log)
    r.clone().text().then(console.log)
    r.text().then(console.log)
    

6、Request、Response及Body混入

Request、Response 都使用了Fetch API的Body混入,以实现两者承担有效载荷的能力。

提供了只读的body属性(实现为ReadableStream)、只读的bodyUsed布尔值(表示流是否已读)。

body混入提供了5个方法,用于将ReadableStream转存到缓冲区的内存里,将缓冲区转换为某种JS类型。

  1. Body.text()
    返回期约,转换为UTF-8格式的字符串
  2. Body.json()
    返回期约,解决为将缓冲区转存得到的JSON
  3. Body.formData()
    浏览器可以将FormData对象序列化、反序列化为主体
  4. Body.arrayBuffer()
  5. Body.blob()
举报

相关推荐

javascript网络请求

0 条评论