JS 中的网络请求 AJAX, Fetch, WebSocket

4,869 阅读9分钟

AJAX 是 Asynchronous JavaScript And XML 的简称,它可以让页面在不刷新的情况下从服务器获取数据。

XMLHttpRequest

浏览器使用XMLHttpRequest对象于服务器通信,它可以使用JSON,XML,HTML和text等格式发送和接收数据。

低版本 IE 浏览器没有XMLHttpRequest对象,但是它可以使用ActiveXObject对象代替。

if (window.XMLHttpRequest) { // IE7+
    XHR = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE 6
    XHR = new ActiveXObject("Microsoft.XMLHTTP");
}

创建 XHR 实例过后就可以监听该实例的状态改变事件onreadystatechange,它会在 XHR 实例的readyState的值改变时触发回调函数。

XHR.onreadystatechange = function () { }

然后我们就可以使用open方法初始化一个请求和send方法发送 HTTP 请求。

XHR.open('GET', 'http://q.com')

// open 方法一共有 5 个参数,method, url, async, user, password 后三个可选。
// async 表示这次是否异步请求,默认是 true

XHR.send()
// send 方法接受一个可选参数 请求主体。
// 参数可以是 FormData, FormData, ArrayBuffer, Document, 序列化字符串

如果是post方法,就要在send之前设置请求头的Content-Type

httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')

// 它接受两个参数 header 和 value

然后我们就要处理服务器返回的数据,回到onreadystatechange函数。它监听readyState属性的变化,而它一共有 5 个值。

  1. 0 表示 请求还未初始化,尚未调用 open() 方法。
  2. 1 表示 已建立服务器链接,open() 方法已经被调用。
  3. 2 表示 请求已接受,send() 方法已经被调用,并且头部和状态已经可获得。
  4. 3 表示 正在处理请求,下载中; responseText 属性已经包含部分数据。
  5. 4 表示 完成,下载操作已完成。

我们还需要关心status属性它也是只读属性,它是这次响应中的 HTTP 数字状态码。在请求之前和 XMLHttpRequest 出错时它为0

responseText 属性是实际的数据,它是字符串,如果相应是 JSON 格式,需要用 JSON 的 parse 处理。

XHR.onreadystatechange = function () {
    if (XHR.readyState === 4 && XHR.status === 200) {
        console.log(JSON.parse(XHR.responseText))
    }
}

如果服务器返回的是 XML, 我们可以用responseXML属性获得数据。

// 如果已指明,responseType 必须是空字符串或 "docuemnt" 
XHR.responseType = 'document';

// overrideMimeType() 用来强制解析 response 为 XML
XHR.overrideMimeType('text/xml');

// --------------

var root = XHR.responseXML.getElementsByTagName('root').item(0)

responseType 属性是一个枚举类型的属性,返回响应数据的类型。

withCredentials 属性是一个Boolean类型,它指示了是否该使用类似cookies,authorization headers(头部授权)或者TLS客户端证书这一类资格证书来创建一个跨站点访问控制请求。

(在IE中,超时属性可能只能在调用 open() 方法之后且在调用 send() 方法之前设置)

abort方法用来终止请求

getAllResponseHeaders方法返回所有的响应头

getResponseHeader(name)方法返回包含指定头文本的字符串

XMLHttpRequset 2

XMLHttpRequset 2 增加了一些新功能。比如能发送FormData

超时时间

timeout 属性是超时时间,单位毫秒。当超时发生时他会触发ontimeout回调函数。

xhr.open('GET', '/server')

xhr.timeout = 2000

xhr.ontimeout = function (e) {}

xhr.send(null);

还有 6 个进度事件。

  1. loadstart 在收到响应的第一个字节触发
  2. progress 在接收期间不断触发
  3. error 发生错误
  4. abort 调用abort方法而终止
  5. load 接收到完整数据
  6. loadend 在通信完成或abort error load事件后触发

load事件就不用readystatechange事件和读取readyState属性。

xhr.addEventListener("progress", updateProgress, false);
xhr.addEventListener("load", transferComplete, false);
xhr.addEventListener("error", transferFailed, false);
xhr.addEventListener("abort", transferCanceled, false);

function updateProgress(event) {
  if (event.lengthComputable) {
    console.log(`${event.position} / ${event.totalSize}`)
  }
}

其中progress的事件对象多了三个属性。

  1. lengthComputable 布尔值 表示进度信息是否可用
  2. position 已经接收到的字节数
  3. totalSize 根据Content-Length预期的字节数

跨域

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

当前网址(http://news.a.com)和请求网址

  1. https://news.a.com 不同协议
  2. http://news.a.com:8080 不同端口
  3. http://home.a.com 不同域名

时就不是同源。

为了使 ajax 可以从不同的网址获取数据。

我们可以使用跨域资源共享(CORS)来解决问题。

在发送请求时会有个Origin头表示请求页面的源信息, 如果服务器返回的Access-Control-Allow-Origin中有相同的源信息或是* 那么就可以跨域请求信息,请求和响应都不包含cookie

CORS通过Preflighted Requests透明服务器验证机制支持使用自定义头部、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-Allow-Max-Age Preflight请求缓存的时间(秒)

默认情况下跨域不提供cookie、HTTP认证、SSL证明,通过withCredentials属性设置为true可以指定某个请求因该发送凭据。 服务器如果接收请求会返回Access-Control-Allow-Credentialstrue的头信息。

还有一种方法是使用JSONP

jsonp方法主要是创建script标签来获得数据,一般通过请求后面跟?callback=fn 回掉函数来获取数据。

Fetch

Fetch 是网络请求的一个更好的替代方法。相比于 XMLHttpRequest,Fetch 写法更简单,功能更强大。

fetch('http://a.com')
  .then(function(response) {
    if (response.ok) {
        return response.json();
    }
    throw new Error('err')
  })
  .then(function(myJson) {
    console.log(myJson);
  })
  .catch(err => {
      console.log(err)
  })

fetch 函数接受两个参数,返回一个 Promise 对象

第一个参数是 URL 或 Request 对象。第二个参数是可选一个配置项对象。

{
    method: 'GET', // 请求方法
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    }, // 头信息
    body: JSON.stringify({data: 1}), // 请求的 body 信息,Blob, FormData 等
    mode: 'cors', // 请求的模式,cors、 no-cors 或 same-origin
    credentials: 'include', // omit、same-origin 或 include。为了在当前域名内自动发送 cookie, 必须提供这个选项
    cache: 'no-cache', // default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached
    redirect: 'follow', // 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向).
    referrer: 'no-referrer', // no-referrer、client或一个 URL。默认是 client。
    referrerPolicy: 'no-referrer', // 指定 referer HTTP头
    integrity: 'sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=', // 包括请求的  subresource integrity 值
}

then 的回调函数接受一个 Response 对象。Response 实现了 Body(代表响应/请求的正文,允许你声明其内容类型是什么以及应该如何处理。)

它有 9 个属性。

  1. type 只读 包含Response的类型 (例如, basic, cors)
  2. url 只读 包含Response的URL
  3. useFinalURL 包含了一个布尔值来标示这是否是该Response的最终URL
  4. status 只读 包含Response的状态码
  5. ok 只读 包含了一个布尔值来标示该Response成功(状态码200-299)
  6. edirected 只读 表示该Response是否来自一个重定向,如果是的话,它的URL列表将会有多个
  7. statusText 只读 包含了与该Response状态码一致的状态信息
  8. headers 只读 包含此Response所关联的Headers 对象
  9. bodyUsed Body 只读 包含了一个布尔值来标示该Response是否读取过Body

8 个方法

  1. clone 创建一个Response对象的克隆
  2. error 返回一个绑定了网络错误的新的Response对象
  3. redirect(url, status) 用另一个URL创建一个新的 response

Body(都返回一个 Promise 实例)

  1. arrayBuffer 接受一个 Response 流, 并等待其读取完成. 并 resolve 一个 ArrayBuffer 对象
  2. blob blob()方法使用一个 Response 流,并将其读取完成
  3. formData 将 Response 对象中的所承载的数据流读取并封装成为一个对象
  4. json 使用一个 Response 流,并将其读取完成。解析结果是将文本体解析为 JSON
  5. text 提供了一个可供读取的"返回流", 它返回一个包含USVString对象,编码为UTF-8

WebSocket

WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开双工、双向通讯会话。

WebSocket 构造函数,接受两个参数,url 和 protocols(可选)。

url 以 ws://wss://(加密)开头

protocols 是 单协议字符串或者包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个WebSocket子协议(例如,您可能希望一台服务器能够根据指定的协议处理不同类型的交互)protocol)。如果不指定协议字符串,则假定为空字符串。

var s = new WebSocket('ws://www.a.com/s.php') // 必须传入绝对URL,可以是任何网站
s.readyState // 0 建立连接 1 已经建立 2 正在关闭 3 连接已关闭或者没有链接成功
s.send('hello') // 发送的数据必须是纯文本
s.onopen = function (){
  console.log('成功建立连接时触发')
}
s.onerror = function () {
  console.log('发生错误,连接不能持续时')
}
s.onmessage = function (event) { // 当接收到消息时
  console.log(event.data) // 数据是纯字符
}
s.close() // 关闭连接
s.onclose = function (event) {
  /*
   * event.wasClean 是否明确的关闭 
   * event.code 服务器返回的数值状态码
   * event.reason 字符串,服务器返回的消息
   */
  console.log('连接关闭时')
}

一共有 10 个属性

  1. binaryType 返回websocket连接所传输二进制数据的类型(blob, arraybuffer)
  2. bufferedAmount 只读 返回已经被send()方法放入队列中但还没有被发送到网络中的数据的字节数。一旦队列中的所有数据被发送至网络,则该属性值将被重置为0。但是,若在发送过程中连接被关闭,则属性值不会重置为0。
  3. extensions 只读 返回服务器选择的扩展名。这当前只是空字符串或连接协商的扩展列表
  4. onclose 用于指定连接失败后的回调函数
  5. onmessage 用于指定当从服务器接受到信息时的回调函数
  6. onopen 用于指定连接成功后的回调函数
  7. protocol 只读 服务器选择的下属协议
  8. readyState 只读 当前的链接状态
  9. url 只读 WebSocket 的绝对路径

2 个方法

  1. close(code, reason) 数字状态码 可选 默认 1005和一个可选的类可读的字符串,它解释了连接关闭的原因。
  2. send(data) 向服务器发送数据(ArrayBuffer,Blob等)