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对象
- 概述
- XHR的属性、方法、事件
- XHR的使用与封装
1.1 概述
- 起初XHR对象是通过
ActiveX
对象实现并包含在MSXML库中 现在的浏览器都通过
XMLHttpRequest
构造函数原生支持XHR对象(低版本IE除外)//IE var xhr1 = new ActiveXObjet('Microsoft.XMLHTTP') //主流浏览器 cosnt xhr2 = new XMLHttpRequest() console.log(xhr2)
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封装
- fetch()方法与基本使用
- Headers对象
- Request对象
- Reponse对象
- Body混入
2.1 fetch()方法与基本使用
- 使用fetch()方法
- 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包含完整URLorigin-when-cross-origin
跨域请求Referrer只包含源
同源请求Referrer包含完整URLstrict-origin
从HTTPS上下文发送至HTTP的请求不包含Referrer头部
其他请求Referrer只包含源strict-origin-when-cross-origin
从HTTPS上下文发送至HTTP的请求不包含Referrer头部
其他跨域请求Referrer只包含源
同源请求Referrer包含完整URLno-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)
初始化
与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);
没有参数时初始化的值为默认值
默认的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()
初始化化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()方法
创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放。