JavaScript使用promise處理多重復(fù)請(qǐng)求
一、為什么要寫這個(gè)文章?
處理重復(fù)請(qǐng)求的文章想必大家也看過(guò)了很多,大多數(shù)都是分為在response返回之前發(fā)現(xiàn)重復(fù)請(qǐng)求就return掉的和使用節(jié)流/防抖來(lái)間接規(guī)避用戶頻繁操作兩種版本的。最近在使用的過(guò)程的中,發(fā)現(xiàn)這兩個(gè)版本在某些場(chǎng)景下還是有些局限性。
二、問(wèn)題場(chǎng)景
如圖,我這個(gè)h5的頁(yè)面,頂部和底部都要顯示這個(gè)名片組件。這些名片的信息是通過(guò)一個(gè)接口來(lái)獲取的,當(dāng)這個(gè)組件在當(dāng)前頁(yè)面被初始化時(shí),就會(huì)發(fā)生兩次重復(fù)的請(qǐng)求。
這時(shí)會(huì)面臨幾個(gè)抉擇:
1. 不對(duì)重復(fù)請(qǐng)求做任何處理。
- 缺點(diǎn)1:造成不必要的資源浪費(fèi),增大服務(wù)器的壓力
- 缺點(diǎn)2:http請(qǐng)求在瀏覽器中是有并發(fā)數(shù)限制的,如果頁(yè)面首屏的請(qǐng)求較多且沒(méi)有分層級(jí)加載的話,很容易造成請(qǐng)求阻塞,影響用戶第一時(shí)間看到主要內(nèi)容
2. 對(duì)重復(fù)請(qǐng)求直接return掉。這也是部分文章的做法,不過(guò)這種做法有種局限性,就是直接認(rèn)定后面的重復(fù)請(qǐng)求均為無(wú)效請(qǐng)求。
- 無(wú)效請(qǐng)求場(chǎng)景:用戶點(diǎn)擊了某個(gè)按鈕進(jìn)行查詢或保存,在請(qǐng)求結(jié)果返回之前,后面點(diǎn)擊基本都算是無(wú)效請(qǐng)求,這種請(qǐng)求就是應(yīng)該被阻止的。當(dāng)然,也可以通過(guò)在按鈕上添加節(jié)流/防抖來(lái)規(guī)避這個(gè)問(wèn)題
- 為何不適用于目前場(chǎng)景:這兩個(gè)名片的組件都是需要數(shù)據(jù)來(lái)渲染的,如果第二次重復(fù)的請(qǐng)求被return了,其中一個(gè)組件的名片就會(huì)沒(méi)有數(shù)據(jù)。
3. 把請(qǐng)求從組件中抽離出來(lái)放到父級(jí)的業(yè)務(wù)頁(yè)面中,再以props的方式傳進(jìn)組件。
- 好處:只需要請(qǐng)求一次,兩個(gè)組件就可以共享一份數(shù)據(jù)。
- 局限性:只適用于單個(gè)業(yè)務(wù)頁(yè)面用到的情況。事實(shí)上這個(gè)組件很多個(gè)業(yè)務(wù)頁(yè)面在用,即使把請(qǐng)求的函數(shù)抽成公用的api,也是要在每個(gè)業(yè)務(wù)頁(yè)面初始化的時(shí)候調(diào)用一次,然后再以props的方式傳進(jìn)組件。
三、解決方式
核心思想
- 初始化一個(gè)handleList的數(shù)組
- 在請(qǐng)求發(fā)送前,根據(jù)入?yún)⑹欠裣嗤袛嗍欠駷橹貜?fù)請(qǐng)求
- 非重復(fù)請(qǐng)求:把改請(qǐng)求的參數(shù)和請(qǐng)求返回的Promise添加至數(shù)組中
- 重復(fù)請(qǐng)求:使用find查找直接返回對(duì)應(yīng)的Promise
- 請(qǐng)求完成后把handleList中之前添加的請(qǐng)求信息移除。
這個(gè)方案是什么都可以使用的,無(wú)論是使用axios、jq、fetch、小程序request。這里就寫實(shí)現(xiàn)的原理,使用時(shí)直接把對(duì)應(yīng)的代碼放到對(duì)應(yīng)的請(qǐng)求時(shí)機(jī)即可。
代碼示例
let handleList = [] // 請(qǐng)求列表 /** * 模擬請(qǐng)求 * @author waldon * @date 2020/6/9 */ const httpRequest = () => { return new Promise((resolve) => { setTimeout(() => { resolve(`請(qǐng)求成功,時(shí)間戳為:${new Date().getTime()}`) }, 1000) }) } /** * 請(qǐng)求的相關(guān)處理 * @author waldon * @date 2020/6/9 * @param {String} url - * @param {Object} requestObj - 請(qǐng)求參數(shù) * @returns {Promise} - 請(qǐng)求的promise */ function requestTest(url, requestObj = {}) { // 因?yàn)槿雲(yún)⒁话悴粫?huì)涉及到復(fù)雜類型,JSON.stringify進(jìn)行序列化對(duì)比其實(shí)夠用了 // 有個(gè)局限性就是入?yún)⒌捻樞蚋淖兞司蜁?huì)影響判斷,不過(guò)這種特殊的改變一般在重復(fù)請(qǐng)求中不會(huì)出現(xiàn) // 實(shí)在是有這種需求的,換成其他遞歸對(duì)比的api,lodash也有類似的api const sameHandle = handleList.find( (item) => item.url === url && JSON.stringify(item.requestObj) === JSON.stringify(requestObj) ) if (sameHandle) { // 遇到相同請(qǐng)求直接返回之前請(qǐng)求的promise console.log(`存在重復(fù)請(qǐng)求,直接返回`) return sameHandle.handle } const handle = new Promise((resolve, reject) => { httpRequest() .then((res) => { resolve(res) }) .catch((err) => { reject(err) }) .finally(() => { // 無(wú)論請(qǐng)求結(jié)果如果,都需要把對(duì)應(yīng)的請(qǐng)求移除掉 handleList = handleList.filter( (item) => item.url !== url && JSON.stringify(item.requestObj) !== JSON.stringify(requestObj) ) }) }) handleList.push({ url, requestObj, handle }) return handle } // *******************************我是華麗的分割線 開(kāi)始使用******************************* const params = { name: 'waldon' } requestTest('/ajax/sameUrl', params).then((res) => { console.log(`首次請(qǐng)求結(jié)果`, res) console.log(`handleList:`, handleList) }) requestTest('/ajax/sameUrl', params).then((res) => { console.log(`重復(fù)請(qǐng)求結(jié)果`, res) console.log(`handleList:`, handleList) // 請(qǐng)求列表中始終只有一個(gè)請(qǐng)求 setTimeout(() => { console.log(`請(qǐng)求完成后的handleList:`, handleList) // 請(qǐng)求完成handleList對(duì)應(yīng)的請(qǐng)求會(huì)被清除 }, 100) }) setTimeout(() => { // 特意延遲500ms請(qǐng)求,因?yàn)槲覀冊(cè)O(shè)置了接口1s才返回,所以應(yīng)該得到一樣的結(jié)果 requestTest('/ajax/sameUrl', params).then((res) => { console.log(`重復(fù)請(qǐng)求結(jié)果`, res) console.log(`handleList:`, handleList) }) }, 500)
輸出結(jié)果
存在重復(fù)請(qǐng)求,直接返回
存在重復(fù)請(qǐng)求,直接返回
首次請(qǐng)求結(jié)果 請(qǐng)求成功,時(shí)間戳為:1621650375540
handleList: [
{
url: '/ajax/sameUrl',
requestObj: { name: 'waldon' },
handle: Promise { '請(qǐng)求成功,時(shí)間戳為:1621650375540' }
}
]
重復(fù)請(qǐng)求結(jié)果 請(qǐng)求成功,時(shí)間戳為:1621650375540
handleList: [
{
url: '/ajax/sameUrl',
requestObj: { name: 'waldon' },
handle: Promise { '請(qǐng)求成功,時(shí)間戳為:1621650375540' }
}
]
重復(fù)請(qǐng)求結(jié)果 請(qǐng)求成功,時(shí)間戳為:1621650375540
handleList: [
{
url: '/ajax/sameUrl',
requestObj: { name: 'waldon' },
handle: Promise { '請(qǐng)求成功,時(shí)間戳為:1621650375540' }
}
]
請(qǐng)求完成后的handleList: []
代碼地址 codepen
https://codepen.io/waldonUB/pen/ZEeeONM
注意的點(diǎn)
- 不要對(duì)response中的數(shù)據(jù)進(jìn)行增刪操作。因?yàn)橹貜?fù)請(qǐng)求返回Promise中的對(duì)象引用地址都是同一個(gè),改動(dòng)了就會(huì)造成數(shù)據(jù)污染。特殊情況時(shí)可以淺拷貝響應(yīng)結(jié)果再處理,或者是增加對(duì)應(yīng)的斷言。
- 處理重復(fù)的請(qǐng)求時(shí),最好在log中提示一下,同時(shí)在組件中注釋好原因和使用場(chǎng)景,避免他人誤改
- 做好極端情況下,請(qǐng)求失敗的處理,設(shè)置有效時(shí)間置空和移除請(qǐng)求信息,避免因?yàn)殚]包堆積過(guò)多無(wú)用的請(qǐng)求信息造成內(nèi)存泄漏。
到此這篇關(guān)于JavaScript使用promise處理多重復(fù)請(qǐng)求的文章就介紹到這了,更多相關(guān)js promise多重復(fù)請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章

JavaScript 閉包機(jī)制詳解及實(shí)例代碼

JS中使用apply方法通過(guò)不同數(shù)量的參數(shù)調(diào)用函數(shù)的方法

js獲取最近一周一個(gè)月三個(gè)月時(shí)間的簡(jiǎn)單示例

Javascript基礎(chǔ)知識(shí)盲點(diǎn)總結(jié)之函數(shù)

JS中postcss插件實(shí)現(xiàn)vw適配的方法

通過(guò)js給網(wǎng)頁(yè)加上水印背景實(shí)例