异步轮询核心是用async/await执行请求、定时器控制节奏、明确退出条件终止;推荐递归settimeout而非setinterval,避免并发问题;支持abortcontroller手动取消及业务字段驱动的退出逻辑。

异步轮询的核心是:用 async/await 执行每次请求,用定时器(如 setTimeout 或 setInterval)控制重试节奏,并通过明确的退出条件(如成功响应、超时、最大重试次数)终止循环。
用 async/await + setTimeout 实现带退出条件的轮询
推荐使用递归式 setTimeout,避免 setInterval 在请求未完成时触发下一次(导致并发或状态错乱)。
关键点:
- 每次轮询前检查退出条件(如响应状态、已耗时、重试计数)
- 请求失败不直接抛错,而是让逻辑决定是否继续
- 用
clearTimeout或返回取消机制应对主动中止场景(可选)
示例代码:
async function pollWithTimeout(url, options = {}) {
const {
maxAttempts = 10,
interval = 1000,
timeout = 30000,
shouldExit = (res) => res?.data?.status === 'done' || res?.data?.error
} = options;
<p>let attempt = 0;
const startTime = Date.now();</p><p>return new Promise((resolve, reject) => {
function attemptPoll() {
// 退出条件 1:超时
if (Date.now() - startTime > timeout) {
return reject(new Error('Polling timeout'));
}
// 退出条件 2:达到最大重试次数
if (attempt >= maxAttempts) {
return reject(new Error('Max attempts exceeded'));
}</p><pre class="brush:php;toolbar:false;"> attempt++;
fetch(url)
.then(res => res.json())
.then(data => {
// 退出条件 3:业务逻辑满足终止条件
if (shouldExit({ data })) {
resolve(data);
return;
}
// 否则继续轮询
setTimeout(attemptPoll, interval);
})
.catch(err => {
console.warn(`Attempt ${attempt} failed:`, err.message);
// 可选择继续,也可在此加失败退出逻辑(如网络错误连续 3 次)
setTimeout(attemptPoll, interval);
});
}
attemptPoll();}); }
// 使用示例 pollWithTimeout('/api/task/status/123', { maxAttempts: 15, interval: 2000, timeout: 60000, shouldExit: ({ data }) => data?.status === 'success' || data?.status === 'failed' }) .then(result => console.log('Done:', result)) .catch(err => console.error('Polling stopped:', err));
用 async/await + 循环(避免回调地狱)
若偏好显式循环结构(更易读、便于调试),可用 while + await + sleep,但需注意不能阻塞主线程——sleep 必须是基于 Promise 的等待函数。
辅助函数:
const sleep = ms => new Promise(r => setTimeout(r, ms));
轮询主逻辑:
async function pollWithLoop(url, options = {}) {
const {
maxAttempts = 10,
interval = 1000,
timeout = 30000,
shouldExit = (res) => res?.status === 'done'
} = options;
<p>const startTime = Date.now();
let attempt = 0;</p><p>while (attempt </p><pre class="brush:php;toolbar:false;"> if (shouldExit(data)) {
return data;
}
} catch (err) {
console.warn(`Attempt ${attempt} failed:`, err.message);
// 可加入错误分类处理,如仅重试网络错误
}
// 等待下一轮,最后一次不 sleep
if (attempt <p>}</p><p>throw new Error(<code>Polling exited: ${attempt} attempts, ${Date.now() - startTime}ms elapsed</code>);
}</p>如何优雅支持手动取消?
在真实场景中,用户可能关闭页面、切换路由或点击“停止轮询”。可通过 AbortController 结合 fetch 实现请求级取消,再配合标志位终止轮询循环。
关键改进:
- 传入
signal到fetch,中断正在进行的请求 - 在每次循环开始前检查
signal.aborted - 调用
controller.abort()即可立即退出
async function pollCancelable(url, { signal, ...options } = {}) {
const controller = new AbortController();
const mergedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
<p>try {
return await pollWithLoop(url, {
...options,
shouldExit: (data) => {
if (signal?.aborted) return true; // 提前退出判断
return options.shouldExit?.(data);
}
});
} finally {
controller.abort(); // 清理
}
}</p><p>// 使用
const controller = new AbortController();
pollCancelable('/api/progress', {
signal: controller.signal,
interval: 500
}).then(...);</p><p>// 外部取消
// controller.abort();</p>
常见退出条件设计建议
退出逻辑不应只依赖 HTTP 状态码(如 200),而应结合业务字段。典型模式:
-
成功态:响应中
status: "completed"、is_ready: true、result != null -
失败态:
status: "failed"、error_code存在、HTTP 5xx 响应(需在 catch 中判断) -
中间态忽略:如
"processing"、"queued"继续轮询 -
兜底防御:始终设置
maxAttempts和timeout,防止无限等待
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











