遞增(增量)輪詢
我們平常開發中,經常用到,輪詢。但是有些場景下, 對某些輪詢有一些額外的要求。 例如我現在公司在做的項目中,後臺有隊列任務,如果隊列任務少,或者空閒的話,那麼這個任務很快就能夠完成, 否則,就需要排隊。
那麼一開始,前臺發送了一個任務執行請求的時候,接着前臺這邊會開始輪詢這個任務的執行狀態,完成了沒有。設定的輪詢時間是5秒。
但是前臺的反饋就會有一個問題,哪怕這個任務在1秒鐘就完成了。 前臺也不會變更狀態,只有等5秒後的下一次請求,然後再變更頁面狀態。
我們這個業務暫時沒有用到 socket, 所以實時性沒有那麼高。
那有沒有別的思路呢?
如果我直接將間隔時間調整爲很短,這樣頁面確實可以較快響應任務的狀態變更。 但是如果任務很多的時候,後臺壓力會很大。
是否可以假設,前臺每次請求執行一個任務後,我先是間隔很短的去輪詢,如果後臺隊列比較空閒,那麼很快就能更新狀態了, 然後我慢慢的遞增間隔的輪詢時間,直到一個設定的最大值。
假設,我起始最少間隔是200毫秒,然後遞增步長是100毫秒,最大間隔是1500毫秒。
現在如果有一個任務請求執行需要1秒,那麼我會分別以以下間隔發送請求:
200ms,200ms+100ms,200ms+100ms+100ms,200ms+100ms+100ms+100ms
四次請求的總時長爲 1400ms, 所以我在第四次請求就能夠更新頁面狀態了,
而如果某個任務請求執行需要10分鐘,那麼前臺會一次累加請求的間隔時間,200ms,300ms,400ms,500ms,.....直到1500ms, 此時如果狀態還沒有完成,那麼後面就每隔1500ms去查詢一次。
實現
根據以上思路,我們有如下實現:
/**
*
* @param min 起始間隔
* @param max 最大延遲
* @param step 步進延遲
* @param cb 回調函數(執行函數)
* @param immediate 是否立即執行
* @param _count (私有屬性,也可以傳遞,傳遞後首次延遲將會是 min + step * _count)
*/
const incrementWalk = function (min: number, max: number, step: number, cb: () => void, immediate = false, _count = 0) {
immediate && cb();
// console.time()
const nextDelay = min + step * _count
setTimeout(() => {
// console.timeEnd()
incrementWalk(min, max, step, cb, immediate, _count + 1)
}, nextDelay > max ? max : nextDelay)
}
const callback = () => {
console.log("trigger")
}
incrementWalk(100, 5000, 300, callback, true)
遞增(增量)輪詢,解決了什麼問題?
解決了在某個任務耗時不確定,又希望儘快刷新頁面狀態,同時又要兼顧服務器壓力問題。
階梯輪詢
解決了上一個問題後,我在想, 有沒有一種情況, 可以指定前幾次請求延時分別不同。 例如, 前10次間隔500ms, 從第10次開始,到第20次結束, 中間每次請求間隔1000ms, 類似這種, 然後按照某個固定間隔輪詢?
所以有了如下實現:
/**
*
* @param tieredArray 二維數組,其數據組成爲 Array<[delay時間,次數]>
* @param constantDelay 固定間隔時間,也就是指定間隔走完了,後面的固定間隔延時
* @param cb 回調函數(待執行函數)
*/
async function tieredWalk(tieredArray: number[][], constantDelay: number, cb: () => void) {
for (let i = 0; i < tieredArray.length; i++) {
const [delay, times] = tieredArray[i]
await new Promise<void>((resolve, reject) => {
function innerWalker(times: number) {
setTimeout(() => {
try {
cb()
} catch (err) {
reject(err)
}
times--
if (times > 0) {
innerWalker(times)
} else {
resolve()
}
}, delay)
}
innerWalker(times)
})
}
outerWalker()
function outerWalker() {
cb()
setTimeout(() => {
console.log("outer")
outerWalker()
}, constantDelay)
}
}
let a = 0
const cb = function () {
a++
if(a > 5) return;
console.log('trigger')
}
tieredWalk([[1000, 3], [200, 2], [2000, 2], [400, 3]], 1000, cb)
這段代碼,
呆逼解決方案
/**
*
* @param tieredArray 二維數組,其數據組成爲 Array<[delay時間,次數]>
* @param constantDelay 固定間隔時間,也就是指定間隔走完了,後面的固定間隔延時
* @param cb 回調函數(待執行函數)
*/
async function tieredWalk(tieredArray: number[][], constantDelay: number, cb: () => void) {
let clearFlag = false
for (let i = 0; i < tieredArray.length; i++) {
const [delay, times] = tieredArray[i]
await new Promise<void>((resolve, reject) => {
function innerWalker(times: number) {
setTimeout(() => {
try {
cb()
} catch (err) {
reject(err)
}
times--
if (times > 0) {
innerWalker(times)
} else {
resolve()
}
}, delay)
}
innerWalker(times)
})
}
outerWalker()
function outerWalker() {
if(clearFlag) return;
cb()
setTimeout(() => {
console.log("outer")
outerWalker()
}, constantDelay)
}
return function(){clearFlag=true}
}
(async function(){
let a = 0
const cb = function () {
a++
if(a > 5) return;
console.log('trigger')
}
const clear = await tieredWalk([[1000, 3], [200, 2], [2000, 2], [400, 3]], 1000, cb)
if(a>5) clear()
})()