寫在開頭
最近負責的項目要接入計費功能,有了計費自然需要充值。說起充值,無外乎支付寶、微信和網銀了,然後前端調用充值接口後,需要起一個輪詢任務,去監聽支付狀態,根據輪詢結果來進行下一步的操作(當然了,有條件的大戶人家想要直接上websocket也沒問題)。所以,業務場景倒是很easy,梳理完邏輯我就吭哧吭哧開搞了…
發現問題
然而,當我基本寫完在調試時,發現每次輪詢停止了,頁面上的狀態會刷新兩次。當時的業務是需要我在支付狀態時顯示驗證碼過期的倒計時,然而倒計時結束後會重新計時一次,這裏的場景可以不關心,直觀的表現就是輪詢清除後狀態更新了兩次,而且是偶現!
import { useRef, useState } from 'react';
export const useRechargeResult = () => {
const intervalRef = useRef<number | undefined>(void 0);
const [rechargeStatus, setRechargeStatus] = useState(0);
const getRechargeStatus = async (sequence: string) => {
await axios.post('/bill/recharge/status', {
sequence
})
.then(res => {
setRechargeStatus(res?.data?.status || 0);
// 1.充值中 2.充值成功 3.充值失敗 4.充值過期
if (res?.data?.status !== 1) {
clearPolling();
}
});
};
const startPolling = (sequence: string) => {
intervalRef.current = setInterval(() => {
getRechargeStatus(sequence);
}, 1000);
};
const clearPolling = () => {
clearInterval(intervalRef.current);
intervalRef.current = void 0;
};
return {
rechargeStatus,
setRechargeStatus,
startPolling,
clearPolling
};
};
bug原因
代碼如上,思考了良久,我覺得是js執行機制哪裏我疏忽了,還有爲什麼是偶現呢?於是乎,經過debuger後,我發現了問題所在。
假如getRechargeStatus
函數沒有then回調,clearInterval(timerId)
可以很輕鬆的清除定時任務,不會讓它進入js執行棧中執行。
如果這樣寫const { data } = await getRechargeStatus()
或者await getRechargeStatus().then(res => {})
,而且js現在處於setTimeout的回調函數已經執行並且在await
狀態中,那麼js清除了setInterval
的回調函數的執行(此時最後一次setTimeout的回調函數已經執行),但是卻沒有清除await getRechargeStatus()
的回調函數,還在苦苦等待…。所以,最後一次輪詢的異步回調(即then中的代碼)會繼續執行,導致了輪詢停止後,頁面狀態又更新了一遍。
請求的時間越長,這個狀態(setTimeout的回調函數已經執行並且在await
狀態中)出現的概率越高。這就是爲什麼,偶現。
所以,我在回調中處理了一下,如果輪詢不存在的話,就不執行,問題解決了,手動狗頭。
const getRechargeStatus = async (sequence: string) => {
await httpBillClient
.post('/bill/recharge/detail', {
sequence
})
.then(res => {
// 清除定時器時,並不能清除異步請求的回調,所以這裏要限制一下
if (intervalRef.current) {
setRechargeStatus(res?.data?.status || 0);
}
// 1.充值中 2.充值成功 3.充值失敗 4.充值過期
if (res?.data?.status !== 1) {
clearPolling();
}
});
};
參考鏈接:https://blog.csdn.net/weixin_34309543/article/details/88838560