先描述两个场景:ios
解决办法其实不少,好比:ajax
但这些方法不可避免的会引入多余状态。若是同页面出现N个接口,状况会更糟糕。如何维护那么多状态呢?json
其实咱们能够在拦截器中解决这些问题,直接贴代码。逻辑看注释:axios
如下以 axios
为例。api
单独封装管理器,是为了拦截器中的代码逻辑更清晰,也为扩展性。假设你须要在其余地方获取全部pending中的请求,并将其所有取消。
注意cancel()
方法中的this.pendings[name].source.cancel()
,要想此方法有效,咱们须要在register
请求时,将ajax工具中包含取消请求api的对象做为source
存入管理器。详见过滤器中代码。
/** * requestManage.js 请求管理器 */ class RequestManage { constructor () { if (!RequestManage.instance) { // 这个属性能够用来判断是人为操做,仍是机器。 this.nerveVelocity = 100 // 进行中的请求 this.pendings = {} RequestManage.instance = this } return RequestManage.instance } /** * 向管理器中注册请求 * @param {String,Number} name - 请求标识 * @param {*} [payload] - 负载信息,用来保存你指望管理器帮你存储的内容 */ register (name, payload = {}) { payload.time = new Date() * 1 this.pendings[name] = payload } /** * 取消请求 * @param {String,Number} name - 请求标识 * @param {*} [payload] - 包含负载信息时,销毁后会从新注册 */ cancel (name, payload) { this.pendings[name].source.cancel() if (payload) { this.register(name, payload) } } /** * 在管理器中移除制定请求 * @param {String,Number} name - 请求标识 */ remove (name) { delete this.pendings[name] } } export default new RequestManage()
// request.js import axios from 'axios import { requestManage } from 'utils' const request = axios.create({ timeout: 5000, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } }) /**------------------------------------------------ * axiox request 拦截器 * 总体逻辑: * 1. 使用请求地址 url 做为请求标识。 * 2. 将 axios 的 source,和包含所有请求参数的字符串存入管理器。(由于source中包含axios的cancel方法) * 3. 请求发起前,查看管理器中是否已存在一个请求?若是不存在,那注册一个进去。 * 4. 若是已经存在,则对比参数,及判断是否为机器。如知足条件,则当前请求不须要发起。抛出错误 currentRequestCancelled。 * 5. 若是参数不一样,或者是人为操做,则视为两个不一样请求。此时取消 pending 中的,并将当前请求从新注册。 * 6. 使用 escape 配置,人为控制一些特殊接口不受约束。 */ request.interceptors.request.use(config => { const { data, params, url, escape } = config const requestTime = new Date() * 1, source = axios.CancelToken.source(), currentBody = `${JSON.stringify(data)}${JSON.stringify(params)}`, pendingRequest = requestManage.pendings[url], pendingBody = pendingRequest && pendingRequest.body, isRobot = pendingRequest && requestTime - pendingRequest.time < requestManage.nerveVelocity if (pendingRequest) { if (currentBody === pendingBody && isRobot) { return Promise.reject(new Error('currentRequestCancelled')) } else if (!escape) { requestManage.cancel(url, { source: source, body: currentBody }) } } else { requestManage.register(url, { source: source, body: currentBody }) } config.cancelToken = source.token return config }, error => { // 请求错误时作些事 return Promise.reject(error) }) /** ------------------------------------------------------------ * axios response 拦截器 * 接口正常返回后,在管理器中把对应请求移除。 * 对 request 时抛出的错误作处理。 */ request.interceptors.response.use(response => { const { url } = response.config requestManage.remove(url) return response }, error => { if (axios.isCancel(error)) { throw new Error('cancelled') } else if (error.message === 'currentRequestCancelled') { throw new Error('currentRequestCancelled') } else { return Promise.reject(error) } }) export default request
// api.js import request from '@/utils/request' /** * escape: true 会跳过全部约束。 * 一般只有一种场景须要这么作: * 页面初始化时,相同接口同时发起多个请求,但参数不一致,且屡次返回的结果都会被使用。若是不设置此项,则只会保留最后一次,前面的请求会被 cancel 掉。 */ export default function (params) { return request({ url: `api_path`, method: 'GET', params: params, // escape: true }) }
import api from 'api.js' async function getData () { try { const req = await api({ a:1, b:2 }) } catch (error) { console.log(error) } } getData() getData() getData() getData() // 屡次调用,控制台中只有第一次请求完成,并打印 `currentRequestCancelled`. (由于这几回请求彻底同样) // 若是不捕获错误,控制台将报 cancelled 或 currentRequestCancelled 错误。
以上仅以 Axios 为例,方法能够扩展到全部请求工具网络