【web通信】Ajax与Fetch

Ajax和Fecth 两者都用于无刷新更新数据:即在不刷新页面的情况下,向服务端发送请求获取数据
AJAX(Asynchronous JavaScript+XML):异步JavaScript加XML

  • 关键是使用XMLHttpRequest(XHR)对象
Fetch:Fetch API提供了一个原生JavaScript接口,用于访问和操纵HTTP管道的一些具体部分
  • Fetch API可以实现XHR对象的所有任务
  • 并且更容易使用,接口也更现代化
原生开发建议使用Fetch,Vue项目使用 axios
axios后续结合Vue介绍
1 XMLHttpRequest对象
  1. 概述
  2. XHR的属性、方法、事件
  3. XHR的使用与封装
1.1 概述
  • 起初XHR对象是通过ActiveX对象实现并包含在MSXML库中
  • 现在的浏览器都通过XMLHttpRequest构造函数原生支持XHR对象(低版本IE除外)
    //IE var xhr1 = new ActiveXObjet('Microsoft.XMLHTTP') //主流浏览器 cosnt xhr2 = new XMLHttpRequest() console.log(xhr2)

    原型上的一些属性与方法
    【web通信】Ajax与Fetch
    文章图片

1.2 XHR的属性、方法、事件
  • 属性
    属性 描述 只读
    readyState 请求状态 1
    onreadystatechange 状态改变时调用 0
    response 响应实体 1
    responseType 定义响应类型 0
    responseText 文本数据 1
    responseURL 响应URL 1
    responseXML 可解析为XML的响应 1
    status 状态码 1
    statusText 完整响应文本 1
    timeout 最大请求时间 0
    ontimeout 请求超时时调用 0
    withCredentials 布尔值,跨域请求是否可带授权信息 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属性
    状态 描述
    0 UNSENT 代理被创建,未调用open()
    1 OPENED open()被调用,send()未调用
    2 HEADERS_RECEIVED send被调用,头部与状态可获得
    3 LOADING 下载中,response可获得
    4 DONE 下载操作完成
1.3 XHR的使用与封装
1.3.1 基本使用

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 允许请求存在时间超出页面生命周期 默认false
method 请求方法 默认GET
mode 决定跨域请求的响应是否有效 下述
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
  • omit
    不发送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
    以伪造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)

【web通信】Ajax与Fetch
文章图片

  • 初始化
    与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时 不允许修改任何头部
  • 禁止修改的头部
  • 简单头部
  • 禁止修改的响应头部
    Set-Cookie
    Set-Cookie2
2.3 Requset对象
Request对象是获取请求资源的接口
2.3.1 创建与克隆 创建Request对象
Request对象的初始化与fetch()方法类似,都接收两个参数
第一个通常为URL,第二个为init
const request = new Request('a') console.log(request);

【web通信】Ajax与Fetch
文章图片

没有参数时初始化的值为默认值
默认的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()

【web通信】Ajax与Fetch
文章图片

初始化化Response对象时可以不添加任何参数
可接收一个body参数与init参数
  • init参数
    headers:Headers实例对象
    status:HTTP响应状态码
    statusText:HTTP响应状态的字符串
type属性
描述
error error静态方法创建
basic 同源响应
cors 跨源响应
opaque no-cors返回的fetch()响应
opaqueredirect redirect设置为manual的请求的响应
克隆
主要使用clone()方法,与克隆Requset对象类似,克隆一个完全一致的副本,同样通过Response构造函数创建的实例在执行text()读取数据后则不能再克隆
2.5 Body混入
Request和Response都使用Fetch API的Body混入,以实现两者承担有效载荷的能力
混入为两个类型提供的内容
  • 只读的body属性-ReadableStream实现
  • 只读的bodyUsed布尔值-标志body流是否已读
  • 一组方法
Body提供的5个方法
都返回Promise
方法 PromiseResult
text() 将缓存区转存得到的UTF-8格式的字符串
json() 将缓存区转存得到的JSON
formData 将缓存区转存得到的formData实例
arrayBuffer() 将缓存区转存得到的ArrayBuffer实例
blob() 将缓存区转存得到的Blob实例
ReadableStream
从TCP/IP角度看,传输的数据是以分块形式抵达端点的,而且速度受到网速的限制。接收端点会为此分配内存并将收到的块写入内存。Fetch API通过ReadableStream支持在这些块到达时就实时的读取和操作这些数据
可以通过ReadableStream构造函数创建一个可读流
  • locked属性
    这个可读流是否被 读取器锁定
  • getReader()方法
    创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放。
【【web通信】Ajax与Fetch】关于Streams API的更多内容

    推荐阅读