摘要:將的,和包含全部請(qǐng)求參數(shù)的字符串存入管理器。如滿足條件,則當(dāng)前請(qǐng)求不需要發(fā)起。如果參數(shù)不同,或者是人為操作,則視為兩個(gè)不同請(qǐng)求。此時(shí)取消中的,并將當(dāng)前請(qǐng)求重新注冊(cè)。如果不設(shè)置此項(xiàng),則只會(huì)保留最后一次,前面的請(qǐng)求會(huì)被掉。
先描述兩個(gè)場(chǎng)景:
快速點(diǎn)擊分頁(yè)碼1.2.3.4.5...。假設(shè)網(wǎng)絡(luò)不好或接口速度不佳,此時(shí)可能有多個(gè)pending中請(qǐng)求。而我們無(wú)法控制返回順序。假如用戶最后點(diǎn)擊到分頁(yè)5,而最后一個(gè)返回的接口是第三頁(yè)的。那現(xiàn)在雖然頁(yè)碼為5,但實(shí)際展示的數(shù)據(jù)卻是第三頁(yè)的。
以Vue為例,created中調(diào)用接口A,某watch中也調(diào)用接口A。那在頁(yè)面初始化時(shí),A可能被調(diào)用了兩次,如果兩次結(jié)果一致,那除了浪費(fèi),也不會(huì)造成其他嚴(yán)重問(wèn)題??山Y(jié)果不一致,會(huì)概率復(fù)現(xiàn)場(chǎng)景1中描述的問(wèn)題。
解決辦法其實(shí)很多,比如:
調(diào)用時(shí)加鎖,判斷該接口是否處于pending?
pending狀態(tài)時(shí),禁用操作按鈕;
但這些方法不可避免的會(huì)引入多余狀態(tài)。如果同頁(yè)面出現(xiàn)N個(gè)接口,情況會(huì)更糟糕。如何維護(hù)那么多狀態(tài)呢?
其實(shí)我們可以在攔截器中解決這些問(wèn)題,直接貼代碼。邏輯看注釋:
以下以 axios 為例。
請(qǐng)求管理器多帶帶封裝管理器,是為了攔截器中的代碼邏輯更清晰,也為擴(kuò)展性。假設(shè)你需要在其他地方獲取所有pending中的請(qǐng)求,并將其全部取消。
注意 cancel() 方法中的 this.pendings[name].source.cancel(),要想此方法有效,我們需要在register請(qǐng)求時(shí),將ajax工具中包含取消請(qǐng)求api的對(duì)象作為 source 存入管理器。詳見過(guò)濾器中代碼。
/** * requestManage.js 請(qǐng)求管理器 */ class RequestManage { constructor () { if (!RequestManage.instance) { // 這個(gè)屬性可以用來(lái)判斷是人為操作,還是機(jī)器。 this.nerveVelocity = 100 // 進(jìn)行中的請(qǐng)求 this.pendings = {} RequestManage.instance = this } return RequestManage.instance } /** * 向管理器中注冊(cè)請(qǐng)求 * @param {String,Number} name - 請(qǐng)求標(biāo)識(shí) * @param {*} [payload] - 負(fù)載信息,用來(lái)保存你期望管理器幫你存儲(chǔ)的內(nèi)容 */ register (name, payload = {}) { payload.time = new Date() * 1 this.pendings[name] = payload } /** * 取消請(qǐng)求 * @param {String,Number} name - 請(qǐng)求標(biāo)識(shí) * @param {*} [payload] - 包含負(fù)載信息時(shí),銷毀后會(huì)重新注冊(cè) */ cancel (name, payload) { this.pendings[name].source.cancel() if (payload) { this.register(name, payload) } } /** * 在管理器中移除制定請(qǐng)求 * @param {String,Number} name - 請(qǐng)求標(biāo)識(shí) */ remove (name) { delete this.pendings[name] } } export default new RequestManage()過(guò)濾器
// 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. 使用請(qǐng)求地址 url 作為請(qǐng)求標(biāo)識(shí)。 * 2. 將 axios 的 source,和包含全部請(qǐng)求參數(shù)的字符串存入管理器。(因?yàn)閟ource中包含axios的cancel方法) * 3. 請(qǐng)求發(fā)起前,查看管理器中是否已存在一個(gè)請(qǐng)求?如果不存在,那注冊(cè)一個(gè)進(jìn)去。 * 4. 如果已經(jīng)存在,則對(duì)比參數(shù),及判斷是否為機(jī)器。如滿足條件,則當(dāng)前請(qǐng)求不需要發(fā)起。拋出錯(cuò)誤 currentRequestCancelled。 * 5. 如果參數(shù)不同,或者是人為操作,則視為兩個(gè)不同請(qǐng)求。此時(shí)取消 pending 中的,并將當(dāng)前請(qǐng)求重新注冊(cè)。 * 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 => { // 請(qǐng)求錯(cuò)誤時(shí)做些事 return Promise.reject(error) }) /** ------------------------------------------------------------ * axios response 攔截器 * 接口正常返回后,在管理器中把對(duì)應(yīng)請(qǐng)求移除。 * 對(duì) request 時(shí)拋出的錯(cuò)誤做處理。 */ 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
// api.js import request from "@/utils/request" /** * escape: true 會(huì)跳過(guò)所有約束。 * 通常只有一種場(chǎng)景需要這么做: * 頁(yè)面初始化時(shí),相同接口同時(shí)發(fā)起多個(gè)請(qǐng)求,但參數(shù)不一致,且多次返回的結(jié)果都會(huì)被使用。如果不設(shè)置此項(xiàng),則只會(huì)保留最后一次,前面的請(qǐng)求會(huì)被 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() // 多次調(diào)用,控制臺(tái)中只有第一次請(qǐng)求完成,并打印 `currentRequestCancelled`. (因?yàn)檫@幾次請(qǐng)求完全一樣) // 如果不捕獲錯(cuò)誤,控制臺(tái)將報(bào) cancelled 或 currentRequestCancelled 錯(cuò)誤。
以上僅以 Axios 為例,方法可以擴(kuò)展到所有請(qǐng)求工具
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103802.html
摘要:一返回值調(diào)用外部方法獲取的值需要對(duì)類型做判斷,因?yàn)槲覀儗?duì)方法返回的值是有期望值類型,但是卻不能保證這個(gè)接口返回的值一直是同一個(gè)類型。 19年目標(biāo):消滅英語(yǔ)!我新開了一個(gè)公眾號(hào)記錄一個(gè)程序員學(xué)英語(yǔ)的歷程 有提升英語(yǔ)訴求的小伙伴可以關(guān)注公眾號(hào):csenglish 程序員學(xué)英語(yǔ),每天花10分鐘交作業(yè),跟我一起學(xué)英語(yǔ)吧 javascript作為一門動(dòng)態(tài)類型語(yǔ)言,具有很高的動(dòng)態(tài)靈活性,當(dāng)定義函數(shù)...
摘要:同時(shí)我們也可以使用控件來(lái)實(shí)現(xiàn)小型的接口自動(dòng)化來(lái)提高接口測(cè)試效率。接口測(cè)試的流程首先我們要了解需求,熟悉業(yè)務(wù)場(chǎng)景然后根據(jù)需求文檔,接口文檔以及業(yè)務(wù)場(chǎng)景來(lái)編寫測(cè)試用例。 ...
摘要:回調(diào)使用封裝入門回調(diào)是啥看這里回調(diào)是什么方應(yīng)杭知乎是一種特殊的函數(shù),這個(gè)函數(shù)被作為參數(shù)傳給另一個(gè)函數(shù)去調(diào)用。 回調(diào)、使用Promise封裝ajax()、Promise入門 1 回調(diào)是啥 call a functioncall a function back callback 看這里:Callback(回調(diào))是什么?---方應(yīng)杭知乎 callback 是一種特殊的函數(shù),這個(gè)函數(shù)被作為參數(shù)...
閱讀 2976·2021-09-23 11:32
閱讀 2938·2021-09-22 15:12
閱讀 1719·2019-08-30 14:07
閱讀 3461·2019-08-29 16:59
閱讀 1651·2019-08-29 11:11
閱讀 2314·2019-08-26 13:50
閱讀 2436·2019-08-26 13:49
閱讀 2630·2019-08-26 11:49