ByteNoteByteNote
setInterval 轮询会叠上一次请求
字

字节笔记本

2026年10月7日 · 约 12 分钟读完

setInterval 轮询会叠上一次请求

API中转
¥120

轮询是隔一段时间再请求一次,直到状态变成完成或次数用尽。对话里先写了一个自己的 PollingClient,用 setInterval 调异步的 poll。setInterval 不会等上一次 fetch 结束。间隔比请求还短时,后一次会叠在前一次上面。

间隔到了,上一次请求可能还在飞

setInterval 不等待 fetch

构造参数里,interval 默认 5000,maxRetries 默认 -1,表示不因为次数停止。stopCondition 默认永远返回假。timeout 默认 0,表示不另设总时长。start 里如果已经有 intervalId 就直接返回,避免两套计时器。

每次 poll 新建一个 AbortController,fetch 带上 signal。状态码不是成功就抛错。成功则 response.json(),再 onSuccess。stopCondition(data) 为真就 stop 并返回。失败时如果是 AbortError 就返回,不计重试。其他错误把 retryCount 加一,调用 onError。maxRetries 不是 -1 而且次数到了,就 stop。

计时器是 setInterval(poll, this.interval)。poll 是 async 函数,定时器触发它之后不会等待那个 Promise。请求要 8 秒、间隔 5 秒时,第二个 fetch 在第一个读完正文之前就发出去了。两个 AbortController 里后一次赋值覆盖 this.controller,stop 只能取消最后那一个,更早的请求还在飞。

maxRetries 只在失败时增加,成功不会清零。中间抖了几次,后面又恢复,次数已经接近上限,再失败一次就停。这和“连续失败才停”不是同一条规则。样本就是按累计失败次数停。

总时长用另一个 setTimeout 调 stop。stop 会 abort、clearInterval、clearTimeout。isPolling 看 intervalId 是否还在。开始时先 await poll() 再启动间隔,所以第一次结束之后才开始计时,但后续每次仍然可能重叠。

使用样本是每 3000 毫秒请求一次,最多 5 次失败,stopCondition 看 data.status === 'completed',timeout 为 30000。状态字段要和接口约定一致,样本用的就是 completed。

已有库按场景挑

对话里接着给了现成库,而不是只维持手写类。

axios-polling 的样本是 axiosPolling({ url, interval: 3000, retries: 3 }),建立在已经用 axios 的项目上。React 里用 useQuery,选项 refetchInterval: 1000,导入来自当时样本里的 react-query。另一条是 useSWR,选项 refreshInterval: 3000。轻量函数是 polling-js 的 poll(() => fetch('/api/data'), { interval: 1000, timeout: 10000 })。

React 界面用带缓存的那两个,轮询和缓存、重新聚焦是一起的。已经用 axios 就用 axios 那条。只想要一个函数、没有界面框架,就用 poll。手写类适合要看清 AbortController 和停止条件的时候,不适合当作生产里的默认选择,因为间隔重叠还在。

退避写在配置里,重叠仍要单独处理

后一版构造函数加上了状态回调 onStateChange,以及退避:enabled、minInterval 默认 1000、maxInterval 默认 30000、factor 默认 2。还有请求和响应两个拦截器,默认是把配置和响应原样返回。currentInterval 从 interval 开始。这些字段说明间隔可以变长,但前一版的 setInterval 在创建时就固定了毫秒数。要让退避生效,下一次等待必须用新的延迟重新安排,而不是改一个已经被定时器捕获的数字。

拦截器适合补头和认错,不负责取消重叠。取消重叠仍然靠:上一次的 Promise 还在就不要发下一次,或者在发下一次之前 abort 上一次。stopCondition 为真时要走 stop,把计时器和飞行中的请求一起收掉。只把状态标记成完成、留下 intervalId,请求还会继续打。

组件卸载或页面离开时调用 stop。否则计时器还在,setState 会打到已经卸掉的组件上。timeout 到了也走同一个 stop,不要另写一套只清计时器、不清 AbortController 的路径。

停止时要同时清计时器和飞行请求

把 start 和 stop 对上

start 开头看 intervalId。已经在轮询就返回,这是防重复启动。然后定义 poll:新建 AbortController,fetch 使用 method、headers、可选的 JSON 正文和 signal。response.ok 为假就抛出带状态码的错误。成功路径读 json,调用 onSuccess,再问 stopCondition。

失败路径先看 error.name === 'AbortError'。主动 stop 造成的中断不要计成失败,也不要 onError。其他错误才 retryCount++。maxRetries 为 -1 时不因次数停止。设成 5 就是累计五次失败后 stop。成功不会把计数清零。

第一次 await poll() 结束后才 setInterval。间隔从第一次完成开始算,这是起点。之后定时器每次调用 poll 都不等待,所以重叠发生在后续,不发生在“第一次相对第二次”的那一下,除非第一次还没从 await 回来。那时还没设置 intervalId。重叠的是间隔已经开始之后的那些次。

timeout 大于 0 时,setTimeout 在总时长到达时 stop。stop 的顺序是:有 controller 就 abort,有 intervalId 就 clearInterval 并置空,有 timeoutId 就 clearTimeout 并置空。置空让 isPolling 变为假,也让下一次 start 可以再来。漏掉 abort,最后一次请求还会回来调用 onSuccess。漏掉 clearInterval,新的 poll 还会被调度。

四个现成入口不要混用选项名

axios-polling 的样本字段是 url、interval、retries。React Query 的样本是 useQuery 的第三个参数 refetchInterval,导入路径写成 react-query。SWR 的字段是 refreshInterval。polling-js 是 poll(函数, { interval, timeout })。四个名字不要互换。把 refetchInterval 写进 SWR,或把 refreshInterval 写进 useQuery,选项会被忽略,请求只发生一次,看起来像轮询坏了。

React 里优先用这两个带缓存的钩子。离开组件时它们和组件生命周期绑在一起。手写类必须在离开时自己 stop。axios 项目用 axios-polling,避免再包一层 fetch。只需要定时调用一个函数时用 polling-js,超时字段样本是 timeout: 10000。

后一版类的退避默认从 1000 毫秒起步,上限 30000,倍数 2,由 enabled 开关。拦截器默认原样返回。这些配置要在安排下一次等待时读 currentInterval。setInterval 在调用时就把延迟固定了,之后修改 currentInterval 不会改已经创建的定时器。退避若要生效,停止旧定时器,用新的延迟再安排下一次,并在上一次 Promise 完成之前不要发出新的 fetch。

onStateChange 用来告诉外面正在轮询还是已经停止。界面根据它禁用按钮。不要只靠 isPolling 在渲染之外去猜,停止是异步的,abort 之后回调还可能再进一次。

下一次要等这一次

手写轮询如果用 setInterval 调用异步 poll,慢请求会叠在一起,stop 也只够到最后一个 AbortController。失败次数按样本是累计的。现成选择按环境分:React 用 refetchInterval 或 refreshInterval,axios 项目用 axios-polling,只需要一个函数就用 polling-js。退避要能改下一次的等待,停止时同时清间隔和飞行请求。

用一个慢接口把重叠看出来。间隔设成样本里的 3000,让处理函数睡得比三秒更久,数一下发出的 fetch。setInterval 那一版会在前一次读完正文之前发出下一次。改成上一次的 Promise 结束才安排下一次之后,飞行中的请求应该始终只有一个。stop 之后不再有新的请求,已经被 abort 的那次以 AbortError 结束,并且不增加 retryCount。

maxRetries 设为 5 时,数的是累计失败。成功穿插在中间也不会把计数清零。stopCondition 看 status 是否为 completed,为真必须调用 stop。总时长 30000 到点走同一条 stop。React 里改用 refetchInterval 或 refreshInterval 时,不要把这两个字段名写错库。离开页面时手写类要自己停,钩子则跟着组件卸掉。

headers、method 和可选的 body 在构造时给定。body 会在请求里被 JSON.stringify。停止条件看返回 JSON 的 status 是否等于 completed。这是样本里的字段,接口若用别的词表示完成,只改 stopCondition,不要改计时器的清法。onSuccess 和 onError 里不要再调用 start,否则 intervalId 还在时会直接返回,或者在停掉之后又开一套计时器。状态变化交给 onStateChange。

相关文章

分享: