先描述兩個場景: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 爲例,方法能夠擴展到全部請求工具網絡