1.Ajax 和 Fetch 介绍
前端向后端发起 Http 请求有 Ajax 和 Fetch 两种方式。Ajax 是一种用于创建快速动态网页的技术,具有局部刷新的特性极大的提升了用户的体验性,避免资源浪费。其缺点是当请求操作嵌套过多时就会产生回调地狱,所以通常在开发中利用 Promise 对 Ajax 工具进行再次封装便于使用。
Fetch API 提供了一个 JavaScript 接口,用于访问和操作 HTTP 管道的一些具体部分,例如请求和响应。它还提供了一个全局 fetch() 方法(fetch 函数挂载在 window 对象下,所以 NodeJS 无法直接使用 fetch),该方法提供了一种简单,合理的方式来跨网络异步获取资源。Fetch 还利用了 Promise 特性,fetch()返回一个 Promise,通过 Promise 链式调用完美避开了回调地狱。
1.1 Fetch 和 Ajax 的区别
- fetch()返回的 promise 将不会拒绝 http 的错误状态,即使响应状态码是 404 或者 500。
- 在默认情况下 fetch 不会接受或者发送 cookies。不能接受跨域 cookies,也不能发起跨域回话。
1.2 常用网络工具介绍
- Axios:Axios 是前端领域最为流行的 Http 请求工具库,它是一个简洁高效的请求库,具有支持 NODE 端和浏览器端、Promise、拦截器与数据处理的等特性,Axios 在浏览器端底层基于 XML HttpRequest 对象处理请求,在 Node 端基于 NodeJS 的 Http 模块处理请求;Axios 不仅简单易用,而且还兼容好,最低兼容 IE7。
- umi-request:网络请求库,基于 fetch 封装, 兼具 fetch 与 axios 的特点,旨在为开发者提供一个统一的 api 调用方式,简化使用,并提供诸如缓存,超时,字符编码处理,错误处理等常用功能。
- fly:一个支持所有 JavaScript 运行环境的基于 Promise 的、支持请求转发、强大的 http 请求库。可以让您在多个端上尽可能大限度的实现代码复用。目前 Fly.js 支持的平台包括:Node.js 、微信小程序 、Weex 、React Native 、Quick App 和浏览器。
2.Ajax 实现原理
XMLHttpRequest 对象是实现 Ajax 的核心,该对象是一种支持异步请求的技术。简单来说,使用 XMLHttpRequest 对象可以通过 JS 向服务器发起请求并处理响应,但不会阻塞用户其他操作。使用 XMLHttpRequest 步骤如下:
- 创建一个 XMLHttpRequest 对象(记得做兼容性处理)。
- 创建一个新的 http 请求,指定该请求的请求方法、请求 URL、是否异步,再通过 XMLHttpRequest 的 send()向服务器发起请求。
- 设置响应 HTTP 请求状态变化的函数。
- 处理响应结果。对于同步请求可以直接根据 XMLHttpRequest 对象的 responseText 属性获取结果,对于异步请求则需要在 XMLHttpRequest 对象的 onreadystatechange 事件中保证请求成功响应后通过 responseText 获取结果。
XMLHttpRequest 常用方法如下:
| 名称 | 描述 |
|---|---|
| onreadystatechange | 当 readyState 属性发生变化时,调用的处理事件。 |
| readyState | 返回一个无符号短整型数字,代表请求的状态码。 |
| response | 返回一个 ArrayBuffer、Blob、Document,或 DOMString,具体是哪种类型取决于 XMLHttpRequest.responseType 的值,其中包含整个响应实体。 |
| responseText | 返回一个 DOMString,该 DOMString 包含对请求的响应,如果请求未成功或尚未发送,则返回 null。DOMString 是一个 UTF-16 字符串。由于 JavaScript 已经使用了这样的字符串,所以 DOMString 直接映射到 一个 String。 |
| responseType | 一个用于定义响应类型的枚举,可选值可参考 https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/responseType |
| responseURL | 返回经过序列化的响应 URL,如果该 URL 为空,则返回空字符 |
| responseXML | 返回一个 Document,其中包含该请求的响应,如果请求未成功、尚未发送或时不能被解析为 XML 或 HTML,则返回 null。 |
| status | 返回一个无符号短整型数字,代表请求的响应状态。 |
| statusText | 返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如:"200 OK")。 |
| timeout | 一个无符号长整型(unsigned long)数字,表示该请求的最大请求时间(毫秒),若超出该时间,请求会自动终止。 |
| upload | 返回一个 XMLHttpRequestUpload 对象,代表上传进度。 |
| withCredentials | 一个布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。 |
| abort() | 如果请求已被发出.则立刻中止请求。 |
| getAllResponseHeaders() | 以字符串的形式返回所有用 CRLF(回车和换行符) 分隔的响应头,如果没有收到响应,则返回 null。 |
| getResponseHeader(name) | 返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。 |
| open(method,url,[async],[user],[password]) | 方法初始化一个请求。该方法要从 JavaScript 代码使用;从原生代码初始化一个请求,使用 openRequest()替代。 |
| send([body]) | 发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回。 |
| setRequestHeader(header,value) | 设置 HTTP 请求头的值。必须在 open() 之后、send() 之前调用 setRequestHeader() 方法。 |
2.1 创建 XMLHttpRequest 对象
/*
1.创建XMLHttpRequest对象。创建时注意兼容性处理,window.XMLHttpReuqest 兼容 IE7+、
Firefox、Chrome、Opera、Safari window.ActiveXObject 兼容 IE6和IE5
*/
const xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: window.ActiveXObject
? new ActiveXObject('Microsoft.XMLHTTP')
: null2.2 创建 Http 请求并发送请求
创建 Http 请求。XMLHttpRequest 通过 open(method,url,async)创建一个请求,method 表示请求方法, url 表示请求路径,async 表示是否异步(为 true 表示异步请求,为 false 表示同步请求),send()用于发送 Http 请求。 注意:由于 GET 请求的请求参数是包含在 url 中,url 大小是有限制的(一般是 2kb),而 POST 请求的请求参数被包含在请求体中(即通过 send 函数传递参数),所以 POST 请求发送数据量比 GET 请求发送数据量大。
// 2.1 创建GET请求,请求参数包含在请求URL中。
xhr.open('get', "http//localhost:8080/test-get?name='zxp'&ag=18", true)
xhr.send() // 发送GET请求
// 2.2 创建POST请求,请求参数包含在请求体,POST请求需要设置请求头
xhr.open('post', 'http://localhost:8080/test-post', true)
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded') // 设置请求头
xhr.send("name='zxp'&age=18") // 发送POST请求,POST请求请求参数写在send()中2.3 设置响应 Http 状态变化函数
XMLHttpRequest 把请求发送到服务器我们并不知道这个 xhr 处于一种什么情况(是发送了请求,还是未发送请求),但可以通过 xhr 的 readyState 属性获取当前 XMLHttpRequest 对象的状态,readyState 有 5 个状态值,分别为 0~4,每个值代表了不同的含义:
- 0 表示未初始,尚未调用 open()。
- 1 表示已经调用 open(),但没有调用 send()。
- 2 表示已经调用 send(),但尚未收到服务器响应。
- 3 表示已经接收服务器部分的响应数据。
- 4 表示已经全部接收服务器响应数据,并可以在客户端使用了。
知道了 xhr 的状态但我们还需要知道这个请求是成功了还是失败了,可以通过 xhr 的 status 属性获取当前 HTTP 请求的状态码,下面仅列举了常用的 HTTP 状态码:
200 表示从客户端发来的请求在服务器端被正常处理了
204 表示请求处理成功,但没有资源返回
301 表示永久性重定向。该状态码表示请求的资源已被分配了新的 URI,以后应使用资源现在所指的 URI。
302 表示临时性重定向。
404 表示服务器上无法找到请求的资源。除此之外,也可以在服务器端拒绝请求且不想说明理由时使用。
500 表示服务器端在执行请求时发生了错误。也有可能是 Web 应用存在的 bug 或某些临时的故障。
上面 2 步了解了之后,可以使用 onreadystatechange 监听 xhr 的 readyState 的变化,从而执行对应的逻辑。整体代码为:
// 使用onreadystatechange监听xhr的readystate变化
xhr.onreadystatechange = function () {
// 判断请求是否发送和响应成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 具体的逻辑处理 ...
// xhr.responseText 获得字符串形式的响应数据。
// xhr.responseXML 获得XML 形式的响应数据。
}
}2.4 处理响应结果
/*
同步请求:发送同步请求可以直接通过XMLHttpRequest对象的responseText属性直接获取响应结果
*/
xhr.open('get', "http//localhost:8080/test-get?name='zxp'&ag=18", false)
xhr.send() // 发送请求你
document.body.innerText = xhr.responseText // xhr.responseText获取响应结果
/*
异步请求:发送异步请求则需要在onreadystatechange事件请求成功响应后通过XMLHttpRequest对象
的responseText属性直接获取响应结果
*/
// 使用onreadystatechange监听xhr的readystate变化
xhr.onreadystatechange = function () {
// 判断请求是否发送和响应成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 具体的逻辑处理 ...
// xhr.responseText 获得字符串形式的响应数据。
// xhr.responseXML 获得XML 形式的响应数据。
console.log(xhr.responseText) // xhr.responseText获取响应结果
}
}2.5 使用 Promise 封装 XMLHttpRequest
export type Method = 'get' | 'GET' | 'options' | 'OPTIONS' | 'post' | 'POST'
enum ResponseType {
// 原始二进制数据缓冲区
ARRAY_BUFFER = 'arraybuffer',
// 文档
DOCUMENT = 'document',
// json对象
JSON = 'json',
// 文本
TEXT = 'text',
// 二进制流
STREAM = 'stream',
// blob
BLOB = 'blob',
}
enum ContentType {
// JSON数据格式
JSON = 'application/json;charset=utf-8',
// FORM表单数据格式
FORM = 'application/x-www-form-urlencoded;charset=utf-8',
// XML数据格式
XML = 'application/xml;charset=utf-8',
// 二进制流数据格式,一般用于文件下载
STREAM = 'application/octet-stream;charset=utf-8',
// pdf数据格式
PDF = 'application/pdf;charset=utf-8',
// word数据格式
WORD = 'application/msword;charset=utf-8',
// html数据格式
TEXT = 'text/html;charset=utf-8',
}
interface RequestConfig {
url?: string
method?: Method
data?: Record<string, any>
headers?: Record<string, any>
timeout?: number
baseUrl?: string
responseType?: ResponseType
withCredentials?: boolean
}
const stringify = (data: Record<string, any>) => {
let params = ''
const arr = Object.keys(data)
if (arr.length === 0) return params
arr.forEach((key) => {
params += `${key}=${data[key]}`
})
return params.slice(0, -1)
}
/**
* 检查url是否是绝对url
* @param url url地址
* @returns 是否是绝对url
*/
const isURL = (url: string) => {
return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\\/?%&=]*)?/.test(url)
}
export const xhr = new XMLHttpRequest() || window.XMLHttpRequest
const defaultConfig: RequestConfig = {
method: 'GET',
data: {},
timeout: 0,
baseUrl: '',
headers: {
'content-type': ContentType.JSON,
},
responseType: ResponseType.JSON,
withCredentials: false,
}
export const request = (config?: RequestConfig) => {
const { url, method, data, headers, timeout, baseUrl, withCredentials } = {
...defaultConfig,
...config,
} as Required<RequestConfig>
const isGet = ['get', 'GET'].includes(method)
const isPost = ['post', 'POST'].includes(method)
// 拼接baseUrl,拼接get请求参数
let requestURL = isURL(url) ? url : baseUrl + url
// 拼接get请求参数
requestURL = isGet ? url + '?' + stringify(data!) : url
// 打开请求
xhr.open(method!, requestURL, true)
// 遍历设置请求头
Object.keys(headers).forEach((key) => {
xhr.setRequestHeader(key, headers[key])
})
isPost && xhr.setRequestHeader('content-type', ContentType.FORM)
// 设置请求超时时间
xhr.timeout = timeout
// 设置跨域请求时是否需要使用凭证
xhr.withCredentials = withCredentials
// 发送请求,对post请求参数处理
xhr.send(isPost ? stringify(data) : '')
return new Promise((resolve, reject) => {
// 监听readyState属性变化
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.responseText)
}
}
// 当请求遇到错误时,将触发error 事件
xhr.onerror = (e: ProgressEvent) => {
reject(new Error(`${e.type}:request was aborted`))
}
})
}
// 测试
setTimeout(() => {
request({
url: 'https://jsonplaceholder.typicode.com/todos/1',
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}, 1000)
setTimeout(() => {
request({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'POST',
data: { name: 'z乘风', age: 18 },
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}, 3000)3.Fetch
Fetch 不是 ajax 的进一步封装,而是原生 js,用于代替 XmlHttpRequest 对象。fetch 函数挂载在 window 对象,且返回一个 Promise,当网络错误时会返回一个 reject 状态的 Promise,否则返回一个 resolve 状态的 Promise。fetch()签名如下:
console.log(window.fetch); // ƒ fetch() { [native code] }
// 签名1:第一个参数为请求URL,参数2是一个配置对象
Promise <Response> fetch(url:string,init?:RequestInit);
// 签名2:第一个参数是Request对象,参数2是一个配置对象,Request与RequestInit具有类似的属性
Promise<Response> fetch(req:Request,init?:RequestInit)3.1 fetch 发送 GET 请求
// 创建请求头对象
const initHeader: Headers = new Headers({
'Content-Type': 'application/json',
})
const initOptions: RequestInit = {
// 请求方法。可选值有GET、POST、PUT、DELETE等等
method: 'GET',
// 请求的头信息。可以是一个Header对象,也可以是
headers: initHeader,
/*
请求主体,可以是一个Blob、BufferSource (en-US)、FormData、URLSearchParams或USVString 对象,
注意:GET 或 HEAD 方法的请求不能包含 body 信息
*/
body: null,
/*
请求模式。可选值有cors、no-cors、same-origin、navigate。
cors(默认):表示允许跨域请求。
no-cors:表示不允许跨域保证请求对应的method只有HEAD、GET、POST 方法,并且请求的
headers 只能有简单请求头。
same-origin:表示允许同源访问,若访问另一个源则会发生错误。
navigate:表示这是一个浏览器的页面切换请求,navigate请求仅在浏览器切换页面时创建,
该请求应该返回HTML
*/
mode: 'cors',
/*
请求的资格证书。如omit、same-origin 或者 include。为了在当前域名内自动发送cookie,必须提供这个选项。
omit:从不向服务器发送cookies
same-origin(默认):只有当URL与响应脚本同源才发送 cookies、HTTP Basic authentication 等验证信息
include:不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、HTTP Basic authentication 等验证信息
*/
credentials: 'omit',
/*
请求缓存模式。可选值有default、no-store、reload 、no-cache 、force-cache、only-if-cached。
default:浏览器从HTTP缓存中寻找匹配的请求。如果缓存匹配上并且有效,它将直接从缓存中返回资源。
如果缓存匹配上但已经过期,浏览器将会使用传统(conditional request)的请求方式去访问远程服务器。
如果服务器端显示资源没有改动,它将从缓存中返回资源。否则,如果服务器显示资源变动,那么重新从服务
器下载资源更新缓存。如果缓存没有匹配,浏览器将会以普通方式请求,并且更新已经下载的资源缓存。
no-store:浏览器直接从远程服务器获取资源,不查看缓存,并且不会使用下载的资源更新缓存
reload:浏览器直接从远程服务器获取资源,不查看缓存,然后使用下载的资源更新缓存。
no-cache:浏览器在其HTTP缓存中寻找匹配的请求。如果有匹配,无论是新的还是陈旧的,浏览器都会向远程服务器
发出条件请求。如果服务器指示资源没有更改,则将从缓存中返回该资源。否则,将从服务器下载资源并更新缓存。
如果没有匹配,浏览器将发出正常请求,并使用下载的资源更新缓存。
force-cache:浏览器在其HTTP缓存中寻找匹配的请求。如果有匹配项,不管是新匹配项还是旧匹配项,都将从缓存中返回。
如果没有匹配,浏览器将发出正常请求,并使用下载的资源更新缓存。
only-if-cached:浏览器在其HTTP缓存中寻找匹配的请求。如果有匹配项(新的或旧的),则从缓存中返回。
如果没有匹配,浏览器将返回一个错误。
*/
cache: 'default',
/*
表示包含?如何处理重定向模式,可选值有follow、error、manual
*/
redirect: 'follow',
}
const url = 'https://jsonplaceholder.typicode.com/todos/1'
fetch(url, initOptions)
.then((res) => {
/**
* res是一个Response对象,具有如下方法:
* ok:返回一个布尔值,表示响应成功(HTTP 状态码的范围在 200-299)。
* status:返回响应Http状态码。
* statusText:返回响应状态文本。
* type:返回响应类型,例如:basic、cors
* error():返回一个新的状态为网络错误的 Response 对象
* arrayBuffer():返回一个将结果解析为ArrayBuffer类型的Promise对象。
* blob():返回一个将结果解析为Blob类型的Promise对象。
* json():返回一个将结果解析为JSON类型的Promise对象。
* text():返回一个将结果解析为文本类型的Promise对象。
* formData:返回一个将结果解析为FormData类型的Promise对象。
*/
if (!res.ok) {
throw new Error('request error!')
}
return res.json()
})
.then((data) => {
console.log(data) // {completed: false,id: 1,title: "delectus aut autem",userId: 1}
})
.catch((err) => {
console.log('err:' + err)
})3.2 fetch 发送 POST 请求
在发送 POST 请求时需要注意以下两点,第一需要将请求头的请求类型设置为"Content-Type":"application/x-www-form-urlencoded","application/x-www-form-urlencoded"表示以表单形式提交请求参数。第二 POST 请求的参数会通过请求主体发送至服务器,对于引用参数需要 JSON.stringif()进行序列化(原始对象转为二进制流的过程叫做序列化,反之叫做反序列化)。
// 创建请求头对象,也可以自定义请求头信息,例如Auth-Token
const initHeader = new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
'Auth-Token': '123asdjibasdgiuqweqwe',
})
// 创建请求对象
const request = new Request(url, {
method: 'POST',
headers: initHeader,
body: JSON.stringify(data), // 通过JSON.strinify()序列化请求参数
mode: 'cors',
credentials: 'omit',
cache: 'default',
} as any)
fetch(request, initOptions)
.then((res) => {
if (!res.ok) {
throw new Error('request error!')
}
return res.json()
})
.then((data) => console.log(data))
.catch((err) => console.log('err:' + err))
// 如果fetch的第一个参数是一个Request对象且传入了配置对象时,那么Request对象会与配置对象进行合并
fetch(request)
.then((res) => {
/**
* res是一个Response对象,具有如下方法:
* ok:返回一个布尔值,表示响应成功(HTTP 状态码的范围在 200-299)。
* status:返回响应Http状态码。
* statusText:返回响应状态文本。
* type:返回响应类型,例如:basic、cors
* error():返回一个新的状态为网络错误的 Response 对象
* arrayBuffer():返回一个将结果解析为ArrayBuffer类型的Promise对象。
* blob():返回一个将结果解析为Blob类型的Promise对象。
* json():返回一个将结果解析为JSON类型的Promise对象。
* text():返回一个将结果解析为文本类型的Promise对象。
* formData:返回一个将结果解析为FormData类型的Promise对象。
*/
if (!res.ok) {
throw new Error('request error!')
}
return res.json()
})
.then((data) => console.log(data)) // {id: 101}
.catch((err) => console.log('err:' + err))3.3 fetch 设置超时时间
XMLHttpRequest 对象提供了超时时间可以拒绝请求时间大于超时时间的请求,但 fetch 并未提供超时时间等 API,不过可以通过 Promise.race()来实现超时时间拒绝请求功能。Promise.race()可以接收多个 Promise 等待最快对象完成,利用这一特性 可以判断请求是否超时,而 AbortController 用于手动终止一个或多个 DOM 请求。
// 创建一个AbortController对象,AbortController对象允许中止一个或多个Web请求
const controller = new AbortController()
// 返回一个AbortSignal对象实例,它可以用来 with/abort 一个Web(网络)请求,需要传递至fetch请求中
let signal = controller.signal
const timeoutPromise = (timeout: number) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 请求超时
resolve(new Response('timeout', { status: 504, statusText: 'timeout ' }))
// 通过AbortController对象的abort()终止一个或多个请求
controller.abort()
}, timeout)
})
}
// fetch请求
const url = 'https://jsonplaceholder.typicode.com/todos/1'
const fetchPromise = () => {
return fetch(url, { signal }) // 传入AbortSignal对象实例
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.log('err:', err))
}
/**
* 请求处理器通过Promise.race()判断请求是否超时,race()可以接收多个Promise,它有点类似于竞速机制,
* 多个Promise一起比赛竞速,谁先执行完毕就返回那个Promise。如果超时Promise最先执行则说明请求超时了,
* 此时需要AbortController对象的abort()终止请求。Promise.race()会返回一个最先执行的Promise对象
*/
Promise.race([timeoutPromise(10), fetchPromise()])
.then((res) => {
console.log(res) // Response {type: 'default', url: '', redirected: false, status: 504, ok: false, …}
})
.catch((err) => {
console.log(err) // err: DOMException: The user aborted a request.
})除了 Promise.race()方式判断请求是否超时外,也可以通过包装 Promise 实现,其核心代码在于promise.then(resolve, reject),通过请求 Promise 控制外部 Promise 的状态,当请求 Promise 执行成功且执行时机优于 setTimeout 回调时,则会调用外部 Promise 的 resolve 将外部 Promise 状态修改为 fulfilled(已成功)。
// 创建一个AbortController对象,AbortController对象允许中止一个或多个Web请求
const controller = new AbortController()
// 返回一个AbortSignal对象实例,它可以用来 with/abort 一个Web(网络)请求,需要传递至fetch请求中
let signal = controller.signal
// fetch请求
const url = 'https://jsonplaceholder.typicode.com/todos/1'
const fetchPromise = fetch(url, { signal })
/**
* @param timeout 超时时间
* @param promise 请求Promise
* @returns 返回一个新的Promise
*/
const timeoutPromise = (timeout: number, promise: Promise<any>) => {
return new Promise((resolve, reject) => {
/**
* 调用外部Promise的resolve和reject回调函数,如果请求promise调用成功则会改变
* 外部Promise的状态(状态改为fulfilled),外部Promise终止执行。
* 若setTimeout的函数执行时机早于promise.then(),则说明请求已超时,外部Promise状态更改
* 为rejected
*/
promise.then(resolve, reject)
setTimeout(() => {
controller.abort() // 中止请求
const err = new Response('timeout', {
status: 504,
statusText: 'timeout ',
})
reject.bind(null, err)
}, timeout)
})
}
timeoutPromise(10, fetchPromise)
.then((res: any) => {
console.log(123)
return res.json()
})
.then((data) => console.log(data))
.catch((err) => console.log('err:', err))4.Axios 封装
5.终止重复请求(扩展)
重复请求是指发送后由于业务处理或者网络阻塞等原因,导致发送相同 URL 和 METHOD 的请求,例如点击按钮发送请求,短时间点击按钮 10 次,假设点击 10 次后第一次的请求仍未响应完成,则前 9 次都属于重复请求,这些请求都是无效的,中止这些无效请求可以避免资源浪费,减少服务器压力和防止网络阻塞。
在 Web 中 AbortController 是最为常见的终止请求方案,各个请求库和 Fetch 都可以通过 AbortController 取消请求。AbortController 接口表示一个控制器对象,允许中止一个或多个 Web 请求,截止目前AbortController仍是实验性的,且兼容性较差。AbortController 的 API:
- AbortController():用于创建一个新的 AbortController,使用 AbortSignal 对象可以完成与 DOM 请求的通信。
- signal:返回一个 AbortSignal 对象实例(AbortSignal 表示一个信号对象),它可以用来 with/abort 一个 Web(网络)请求。
- about():中止一个尚未完成的 Web(网络)请求,这能够中止 fetch 请求及任何响应体的消费和流。
AbortController 的使用步骤:首先通过 AbortController()构造函数来创建一个 controller 实例,然后通过 AbortController.signal 属性获取到它的关联对象 AbortSignal 的引用,在发起请求时将 AbortController.signal 作为请求选项参数传入,并与请求关联起来,此时就可以使用 AbortController.abort()中止与之关联的请求。
5.1 XMLHttpRequest 中止请求
XMLHttpRequest 对象通过 about()终止请求。
setTimeout(() => {
request({
url: 'https://jsonplaceholder.typicode.com/todos/1',
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
xhr.abort() // 中止请求
}, 1000)
setTimeout(() => {
request({
url: 'https://jsonplaceholder.typicode.com/posts',
method: 'POST',
data: { name: 'z乘风', age: 18 },
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})
}, 3000)
5.2 Fetch 中止请求
通过 AbortController 中止 Fetch 请求。
// 实例化AbortController对象
const controller = new AbortController()
// 获取signal(信号)
const signal = controller.signal
fetch('https://jsonplaceholder.typicode.com/posts/1', {
signal,
})
.then((response) => response.json())
.then((json) => console.log(json))
controller.abort() // 中止请求
5.3 Axios 中止请求
在 Axios 中止请求分为 AbortController 和 CancelToken 两种方式。对于 Axios v0.22.0 及以上版本,官方推荐使用 AbortController 取消请求,CancelToken 从 v0.22.0 开始已被弃用,官方不推荐在 v0.22.0 及以上版本中使用。
5.3.1 AbortController 中止 Axios 请求
import axios from 'axios'
// 实例化AbortController对象
const controller = new AbortController()
// 获取signal(信号)
const signal = controller.signal
axios('https://jsonplaceholder.typicode.com/posts/1', {
signal,
}).then((res) => {
console.log(res)
})
controller.abort() // 中止请求
5.3.2 CancelToken 中止 Axios 请求
/*
* 方式1:通过source()工厂方法创建一个Cannel Token
*/
import axios from 'axios'
const CancelToken = axios.CancelToken
// 通过CancelToken.source 工厂方法创建一个 cancel token
const source = CancelToken.source()
axios('https://jsonplaceholder.typicode.com/posts/1', {
cancelToken: source.token,
})
.then((res) => {
console.log(res)
})
.catch((err) => {
if (axios.isCancel(err)) {
console.log('Request canceled', err.message)
}
})
// 中止请求,参数可选
source.cancel('cancel request')/*
* 方式2:通过传递一个 executor 函数到 CancelToken 的构造函数来创建
* 一个 cancel token
*/
import axios, { Canceler } from 'axios'
const CancelToken = axios.CancelToken
let cancel: Canceler
axios('https://jsonplaceholder.typicode.com/posts/1', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c
}),
})
.then((res) => {
console.log(res)
})
.catch((err) => {
if (axios.isCancel(err)) {
console.log('Request canceled', err.message)
}
})
cancel!('cancel request') // 中止请求
5.4 umi-request 中断请求
// 按需决定是否使用 polyfill,该依赖支持事件处理的abortcontroller的垫片
import 'yet-another-abortcontroller-polyfill'
import Request from 'umi-request'
const controller = new AbortController() // 创建一个控制器
// 返回一个 AbortSignal 对象实例,它可以用来 with/abort 一个 DOM 请求
const { signal } = controller
signal.addEventListener('abort', () => {
console.log('aborted!')
})
Request('/api/response_after_1_sec', {
// 这将信号和控制器与获取请求相关联然后允许我们通过调用 AbortController.abort() 中止请求
signal,
})
// 取消请求
setTimeout(() => {
// 中止一个尚未完成的DOM请求。这能够中止 fetch 请求,任何响应Body的消费者和流。
controller.abort()
}, 100)6.拦截请求(扩展)
拦截请求作为一个扩展知识点,例如数据 MockJS 拦截请求,当在 MockJS 定义请求 URL 和请求方法及响应式时,若发送请求的 URL 与方法和 MockJS 定义的接口一致时,MockJS 会拦截请求并将定义的响应式返回,这是 MockJS 的处理流程,而 WebRequest 提供了拦截 Http 请求的机制,WebRequest 可以用于获取请求及其返回的 header 和 body、取消或重定向请求、修改请求及其返回的 header。
