一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
今天来记录一下业务中遇到的问题,如何实现前端轮询?轮询在前端中并不少见,实现的方案也多种多样,可能一开始都想到用定时器setInterval来实现,每隔多少秒发送一个请求,但是请求也需要时间,所以可能请求回来了还是会发一个请求出去,可能有点问题,但是这么实现也是咩有问题的,也能满足我们的需求。
实际上轮询并不是一个单纯的定时方法,而是依赖前一次返回结果来决定,更像是一个连续的链式调用,所以我们可以通过尾递归来实现,结合我们技术栈是React,所以我们可以通过一个自定义hooks来实现。
思路如下:
- 我们传入一个请求方法
polling,希望返回值是一个Promise,用来await他的结果,根据返回值来判断是否需要轮询,在这我们可以定义一个hasFinshed变量来进行标识 - 这个自定义hooks返回一个方法
doPolling,调用这方法时来轮询这个接口,但我们某些情况下也需要取消轮询,因此我们定义了一个cancelPolling方法 - 在方法
doPolling中定义了一个pollNext方法,用来递归调用我们的请求方法polling - 为了实现取消方法,我们定义了
isPollingRef和cancelRef两个Ref来作为标记,每次轮询前,判断是否取消了轮询,是就直接返回,不轮询,否则进行轮询 - 为了防止并发场景,多个接口同时轮询可能会产生bug,所以定义了
isPollingRef,判断是否正在轮询,是则返回,否则就轮询 - 增加
useUnMount钩子,在组件卸载时取消轮询
// 轮询接口
import { useCallback, useRef } from "react";
import useUnMount from './useUnMount'
import { sleep } from '../utils'
const usePolling = (polling: () => Promise<any>): [doPolling: () => void, cancelPolling: () => void] => {
const isPollingRef = useRef(false)
const cancelRef = useRef(false)
const doPolling = useCallback(() => {
// 是否正在轮询,是,返回,不能多个接口同时轮询,避免这些接口有先后顺序相互影响
if (isPollingRef.current) {
console.log(`[doPolling] isPolling, return immediately`);
return;
}
isPollingRef.current = true;
const pollNext = async () => {
// 如果掉了取消轮询,那么就返回不执行
if (cancelRef.current) {
isPollingRef.current = false;
cancelRef.current = false;
return;
}
// 发送请求,返回值组装一下,给个 hasFinshed 判断是否还要继续轮询
const { hasFinshed } = await polling();
if (!hasFinshed) {
await sleep(30000);
pollNext()
} else {
isPollingRef.current = false;
}
};
pollNext()
}, [])
const cancelPolling = useCallback(() => {
if (isPollingRef.current) {
cancelRef.current = true;
}
}, [])
useUnMount(cancelPolling);
return [doPolling, cancelPolling]
};
export default usePolling;
// 组件卸载时的钩子,componentWillUnMount
import { useEffect } from "react"
const useUnMount = (fn: Function): void => {
useEffect(() => () => { fn() }, [])
};
export default useUnMount;