5

【web通信】Ajax与Fetch

 2 years ago
source link: https://segmentfault.com/a/1190000040841472
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Ajax和Fecth

两者都用于无刷新更新数据:即在不刷新页面的情况下,向服务端发送请求获取数据

AJAX(Asynchronous JavaScript+XML):异步JavaScript加XML

  • 关键是使用XMLHttpRequest(XHR)对象

FetchFetch API提供了一个原生JavaScript接口,用于访问和操纵HTTP管道的一些具体部分

  • Fetch API可以实现XHR对象的所有任务
  • 并且更容易使用,接口也更现代化

原生开发建议使用Fetch,Vue项目使用axios
axios后续结合Vue介绍

1 XMLHttpRequest对象

  1. XHR的属性、方法、事件
  2. XHR的使用与封装

1.1 概述

  • 起初XHR对象是通过ActiveX对象实现并包含在MSXML库中
  • 现在的浏览器都通过XMLHttpRequest构造函数原生支持XHR对象(低版本IE除外)

    //IE
    var xhr1 = new ActiveXObjet('Microsoft.XMLHTTP')
    //主流浏览器
    cosnt xhr2 = new XMLHttpRequest()
    console.log(xhr2)

    原型上的一些属性与方法
    image.png

1.2 XHR的属性、方法、事件

  • 属性

    属性描述只读readyState请求状态1onreadystatechange状态改变时调用0response响应实体1responseType定义响应类型0responseText文本数据1responseURL响应URL1responseXML可解析为XML的响应1status状态码1statusText完整响应文本1timeout最大请求时间0ontimeout请求超时时调用0withCredentials布尔值,跨域请求是否可带授权信息0
  • 方法

    方法描述open(methond,url,async,user,password)初始化请求send(body)发送请求abort()如果请求发出,终止请求overrideMimeType(mimeType)覆盖服务器返回的MIME类型setReuquestHeader(header,value)设置HTTP请求头部,在open与send之间使用
  • 事件

    事件描述loadstart接收到响应数据时触发progress请求接收到更多数据时,接收响应期间反复触发error请求出错触发abort请求被停止时触发load请求成功时触发loadend请求完成触发,不管成功失败timeout在预设时间内没有收到响应时触发
  • readState属性

    值状态描述0UNSENT代理被创建,未调用open()1OPENEDopen()被调用,send()未调用2HEADERS_RECEIVEDsend被调用,头部与状态可获得3LOADING下载中,response可获得4DONE下载操作完成

1.3 XHR的使用与封装

1.3.1 基本使用

<!-- html -->
<button id="req">请求</button>
const btn = document.getElementById('req')

const xhr = new XMLHttpRequest()
btn.addEventListener('click', () => {
    xhr.addEventListener('readystatechange', () => {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                console.log(xhr.response);
            } else {
                console.log(xhr.readyState, xhr.status);
            }
        }
    })
    xhr.open('GET', 'http://localhost:8000', true)
    xhr.send()
    xhr.responseType = 'json'
})

当需要在请求成功后再进行其他请求,结构会很臃肿,需要进一步的封装
ajax请求跨域问题这里使用的是CORS(Cross-Origin Resource Sharing 跨源资源共享)方式解决,在服务端的响应头部添加相关字段

1.3.2 封装

回调封装

  • function ajaxCallback(url, fnSucc, fnFail) {
      const xhr = new XMLHttpRequest()
      xhr.addEventListener('readystatechange', () => {
          if (xhr.readyState === 4) {
              if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                  fnSucc(xhr.responseText)
              } else if (fnFail) {
                  fnFail(xhr.status)
              } else {
                  console.log('请求出错');
              }
          }
      })
      xhr.open('GET', url, true)
      xhr.send()
    }
  • btn.addEventListener('click', () => {
      ajaxCallback('http://localhost:8000', res => {
          console.log(res);
          ajaxCallback('http://localhost:8085', res => {
              console.log(res);
          })
      })
    })

    这种方式又会出现一个经典的问题-回调地狱,下面我们使用Promise再次封装

Promise封装

  • function ajaxPromise(url) {
      return new Promise((resolve, reject) => {
          const xhr = new XMLHttpRequest()
          xhr.addEventListener('readystatechange', () => {
              if (xhr.readyState === 4) {
                  if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                      resolve(xhr.responseText)
                  } else {
                      reject(xhr.status)
                  }
              }
          })
          xhr.addEventListener('error', () => {
              reject('请求出错');
          })
          xhr.open('GET', url, true)
          xhr.send()
      })
    }
  • btn.addEventListener('click', () => {
      ajaxPromise('http://localhost:8000')
          .then(val => {
              console.log(val);
              return ajaxPromise('http://localhost:8085')
          })
          .then(val => {
              console.log(val);
          })
          .catch(res => {
              console.log(res);
          })
    })

    这种链式调用方式可以避免回调地狱,但有时其执行顺序不太容易理解

2 Fetch API

Fetch API除了请求数据外,也可以在服务端使用,提供拦截、重定向和修改通过fetch()生成的请求

  • fetch()方法返回Promise,不需要再进行Promise封装
  1. fetch()方法与基本使用
  2. Headers对象
  3. Request对象
  4. Reponse对象
  5. Body混入

2.1 fetch()方法与基本使用

  1. 使用fetch()方法
  2. fetch()自定义配置

2.1.1 使用fetch()方法

    fetch('http://localhost:8000')
        .then(response => response.text())
        .then(data => {
            console.log(data);
        })
  • fetch的第一个参数是url,执行返回一个Promise
  • 第一个then返回Response的实例化对象,调用其text()方法也会返回一个Promise
  • 第二个then可以接收到实际的数据

    fetch从执行到获取到数据实际上进行了两层的Promise链式执行,并不是直接获得数据

2.1.2 fetch()自定义配置

在只有一个url参数时进行的是默认的操作,方法使GET,fetch提供了第二个参数,以对象形式进行自定义配置

选项描述值body指定请求体的内容Blob、BufferSource、FormData、URLSearchParams、ReadableStream、String的实例cache控制浏览器与HTTP缓存的交互下述credentials指定请求中如何包含cookie下述headers指定请求头Headers对象实例integrity强制资源完整包含子资源完整性标识符的字符串keepalive允许请求存在时间超出页面生命周期默认falsemethod请求方法默认GETmode决定跨域请求的响应是否有效下述redirect如何处理重定向响应下述referrer指定HRRP的Referrer头部内容下述referrerPolicy指定Referrer头部内容下述signal支持通过AbortController中断进行中的fetch()请求默认未关联控制器的AbortSingle实例

cache

  • default(默认)

    命中有效缓存,不发送请求;命中无效缓存,发送条件请求更新缓存;未命中缓存发送请求

  • no-store

    不检查缓存,直接发送请求;不缓存响应

  • reload

    不检查缓存,直接发送请求;缓存响应

  • no-cache

    命中有效/无效缓存,发送条件请求并更新缓存;未命中缓存发送请求并缓存响应

  • force-cache

    命中有效/无效缓存,不发送请求;未命中缓存发送请求并缓存响应

  • only-if-cached(仅在mode:same-origin时使用)

    命中有效/无效缓存,不发送请求;未命中缓存返回504(网关超时)的响应

credentials
类似于XHR中的withCredentials

  • 不发送cookie

  • same-origin(默认)

    同源下发送cookie

  • include

    同源跨源都发送cookie
    服务端Set-Cookie需要设置sameSite=None;secure

mode

  • cors(默认)

    允许遵守CORS协议的跨域请求

  • no-cors

    允许不需要发送预请求的跨源请求

  • same-origin

    禁止任何跨源请求

  • navigate

    用于支持HTML导航,只在文档间导航时使用。基本用不到

redirect

  • follow(默认)

    跟踪重定向请求,以最终非重定向URL的响应作为最终响应

  • error

    重定向请求抛出错误

  • manual

    不跟踪重定向,返回opaqueredirect类型响应,同时任然暴露期望的重定向URL,允许以手动方式跟踪重定向

referrer

  • no-referrer

    以no-referrer作为值

  • client/about:client(默认)

    以当前URL或no-referrer作为值

  • 以伪造URL作为值

ReferrerPolicy

  • no-referrer

    所有请求不包含Referrer头部

  • unsafe-url

    所有请求Referrer包含完整URL

  • origin

    所有请求Referrer只包含源

  • same-origin

    跨域请求不包含Referrer
    同源请求Referrer包含完整URL

  • origin-when-cross-origin

    跨域请求Referrer只包含源
    同源请求Referrer包含完整URL

  • strict-origin

    从HTTPS上下文发送至HTTP的请求不包含Referrer头部
    其他请求Referrer只包含源

  • strict-origin-when-cross-origin

    从HTTPS上下文发送至HTTP的请求不包含Referrer头部
    其他跨域请求Referrer只包含源
    同源请求Referrer包含完整URL

  • no-referrer-when-downgrade(默认)

    从HTTPS上下文发送至HTTP的请求不包含Referrer头部
    其他请求包含完整URL

2.2 Headers对象

Headers是所有外发请求和入站响应头部的容器,Request和Response实例都有一个Headers实例
Headers对象与Map对象极其相似,都包含get、set、has、delete等实例方法

2.2.1 Headers上的API

const headers = new Headers({
    foo:'bar'
})
console.log(headers)

image.png

  • 初始化

    与Map不同的是Headers可以使用对象进行初始化

  • append

    set方法是添加或者更新一个值,append是添加或追加一个值
    因为在Header中一个字段可以有多个值
    多个值用,分隔

2.2.2 头部护卫

并非所有HTTP头部都可以被客户端修改

护卫激活限制none初始化Headers实例时无request初始化Request对象,mode非no-cors时不允许修改禁止修改的头部request-no-cors初始化Request对象,mode为no-cors时不允许修改简单头部response初始化Response时不允许修改禁止修改的响应头部immutable通过error()或redirect()初始化Response时不允许修改任何头部

2.3 Requset对象

Request对象是获取请求资源的接口

2.3.1 创建与克隆

创建Request对象
Request对象的初始化与fetch()方法类似,都接收两个参数

第一个通常为URL,第二个为init

const request = new Request('a')
console.log(request);

image.png

没有参数时初始化的值为默认值
默认的url为当前origin,headers为空

使用Request构造函数克隆Request对象

const req1 = new Request('http://localhost:8000', { body: 'foo', method: 'POST' })
const req2 = new Request(req1)
console.log(req1, req2);
console.log(req1.bodyUsed,req2.bodyUsed) //true false

克隆的副本并不会与源对象完全一致,克隆之后源对象的bodyUsed会变为true

使用clone()方法克隆Request对象

const req1 = new Request('http://localhost:8000', { body: 'foo', method: 'POST' })
const req2 = req1.clone()
console.log(req1, req2);
console.log(req1.bodyUsed, req2.bodyUsed);//false false

使用clone方法不会改变源对象bodyUsed的值

  • 如果请求对象的bodyUsed值为true(请求体已被读取)时,不能克隆

    //使用Request克隆后不能再对源对象进行克隆
    const req1 = new Request('http://localhost:8000', { body: 'foo', method: 'POST' })
    const req2 = req1.clone() //未改变bodyUsed
    const req3 = new Request(req1) //改变bodyUsed
    const req4 = req1.clone() || new Request(req1) // TypeError
    //使用text()读取后不能再克隆
    const req1 = new Request('http://localhost:8000', { body: 'foo', method: 'POST' })
    req1.text()
    cosnt req2 = req1.clone() //TypeError

    不管是用text()读取了数据还是Request克隆导致bodyUsed变化,之后都不能再对源对象进行克隆的操作

2.3.2 在fetch()中使用Request对象

可以看出Request构造函数与fetch()拥有相同的函数签名

  • 调用fetch()时,第一个参数可以不是URL而是一个Request实例对象
  • 第二个参数会复制Request的init,可以再添加进行覆盖

    //自定义头部
    const headers = new Headers({
      foo: 'bar',
    })
    headers.append('foo', 'baz')
    //初始化Request
    const req = new Request('http://localhost:8000', {
      body: 'moo',
      method: 'POST',
      credentials: 'include',
      headers
    })
    //fetch()中使用Requset对象
    fetch(req.clone())
      .then(response => response.text())
      .then(data => {
          console.log(data);
      })
    fetch(req.clone,{mode: 'no-cors',})

    如果不进行克隆就只能进行一次请求,源Request对象被读取后不能再使用
    在fetch()中使用克隆的Request对象,可以进行多个请求

2.4 Response对象

Response对象是获取资源响应的接口

创建

//Response()构造函数创建
const res1 = new Response()
console.log(res1);
//Response.redirect()创建
const res2 = Response.redirect(url,301)
//Response().error()创建
const res3 = Response.error()

image.png

初始化化Response对象时可以不添加任何参数
可接收一个body参数与init参数

  • init参数

    headers:Headers实例对象
    status:HTTP响应状态码
    statusText:HTTP响应状态的字符串

type属性

值描述errorerror静态方法创建basic同源响应cors跨源响应opaqueno-cors返回的fetch()响应opaqueredirectredirect设置为manual的请求的响应

克隆
主要使用clone()方法,与克隆Requset对象类似,克隆一个完全一致的副本,同样通过Response构造函数创建的实例在执行text()读取数据后则不能再克隆

2.5 Body混入

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

混入为两个类型提供的内容

  • 只读的body属性-ReadableStream实现
  • 只读的bodyUsed布尔值-标志body流是否已读

Body提供的5个方法
都返回Promise

方法PromiseResulttext()将缓存区转存得到的UTF-8格式的字符串json()将缓存区转存得到的JSONformData将缓存区转存得到的formData实例arrayBuffer()将缓存区转存得到的ArrayBuffer实例blob()将缓存区转存得到的Blob实例

ReadableStream

从TCP/IP角度看,传输的数据是以分块形式抵达端点的,而且速度受到网速的限制。接收端点会为此分配内存并将收到的块写入内存。Fetch API通过ReadableStream支持在这些块到达时就实时的读取和操作这些数据

可以通过ReadableStream构造函数创建一个可读流

  • locked属性

    这个可读流是否被读取器锁定

  • getReader()方法

    创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放。

关于Streams API的更多内容


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK