轮询(Polling)是一种长久而有效的技术,用于从服务器获取最新数据,例如订单状态、消息通知、实时报表等。最简单的实现方式是使用 setInterval,每隔固定时间就发送一次请求。
// 简单粗暴的轮询
setInterval(fetchOrderStatus, 2000); // 每2秒查询一次
这种轮询方式存在诸多弊端:
- 资源浪费:无论数据是否更新,无论用户是否在看,请求都会不间断地发送。
- 服务器压力:成千上万个客户端以高频率轮询,会对服务器造成巨大压力。
- 请求重叠:如果一个请求的响应时间超过了轮询间隔(例如网络慢),新的请求就会在旧的请求还未完成时发出,导致请求堆积。
那么,我们如何让轮询变得“智能”起来,既能及时获取数据,又能最大化地节省资源呢?
策略一:使用 setTimeout替代 setInterval(基础优化)
这是最基础也是最重要的改进。setInterval 不关心上一次请求是否完成,它只会死板地按时执行。而通过递归调用的 setTimeout,我们可以确保下一次请求一定是在上一次请求完成之后再发起。
function poll() {
fetch('/api/fedjavascript')
.then(res => res.json())
.then(data => {
// 处理数据...
console.log(data);
})
.catch(error => console.error('请求失败:', error))
.finally(() => {
// 在上一次请求完成后,再设置下一次轮询
setTimeout(poll, 2000);
});
}
// 启动轮询
setTimeout(poll, 2000);
优点:完美解决了请求重叠的问题,保证了请求的顺序性和间隔的有效性。
策略二:指数退避(Exponential Backoff)- 优雅地处理错误
当服务器出现故障或网络中断时,如果客户端仍然以固定频率不断请求,这无异于“伤口上撒盐”。指数退避策略可以在出现错误时,逐步增加轮询的间隔,待服务恢复后再恢复正常频率。
let errorCount = 0;
const BASE_INTERVAL = 2000; // 基础间隔2秒
const MAX_INTERVAL = 60000; // 最大间隔60秒
function pollWithBackoff() {
fetch('/api/fedjavascript')
.then(res => {
if (!res.ok) throw new Error('服务器响应异常');
return res.json();
})
.then(data => {
// 成功后,重置错误计数器和间隔
errorCount = 0;
console.log('数据获取成功:', data);
scheduleNextPoll();
})
.catch(error => {
// 失败后,增加错误计数器
errorCount++;
console.error('请求失败,将延长下次请求时间');
scheduleNextPoll();
});
}
function scheduleNextPoll() {
// 计算退避间隔:2s, 4s, 8s, 16s... 直到最大值
const interval = Math.min(BASE_INTERVAL * Math.pow(2, errorCount), MAX_INTERVAL);
setTimeout(pollWithBackoff, interval);
console.log(`下一次请求将在 ${interval / 1000} 秒后发起`);
}
// 启动
scheduleNextPoll();
优点:在系统不稳定时能显著降低客户端和服务器的压力,实现“智能容错”。
策略三:利用 Page Visibility API - 当用户“不在看”时放慢节奏
如果用户切换到了其他浏览器标签页,或者最小化了浏览器,我们还有必要以高频率轮询吗?显然没有。Page Visibility API 可以告诉我们页面是否对用户可见。
let pollerId; // 用于存储 setTimeout 的 ID
function scheduleNextPoll() {
// 如果页面不可见,使用更长的轮询间隔(例如30秒)
const interval = document.hidden ? 30000 : 2000;
clearTimeout(pollerId); // 清除旧的定时器
pollerId = setTimeout(pollWithBackoff, interval);
console.log(`页面${document.hidden ? '不可见' : '可见'}。下一次请求将在 ${interval / 1000} 秒后发起`);
}
// 监听页面的可见性变化
document.addEventListener('visibilitychange', () => {
// 当页面从隐藏变为可见时,可以立即触发一次轮询
if (!document.hidden) {
console.log('页面恢复可见,立即执行一次轮询');
pollWithBackoff();
} else {
console.log('页面已切换到后台');
}
});
// 启动
scheduleNextPoll();
优点:极大地节省了用户设备的 CPU 和电量,也减少了不必要的服务器请求。这是现代 Web 应用优化的一个重要手段。
虽然我们可以让轮询变得非常智能,但它本质上仍然是“客户端拉取”模型。对于严格要求实时性的场景,现代技术也为我们提供了其他的选择:
- WebSockets:建立一个持久的双向连接。服务器可以随时主动向客户端推送数据,无需客户端反复询问。这是实时聊天、在线游戏等场景的首选。
- Server-Sent Events (SSE):一个轻量级的 WebSocket 替代品,用于从服务器到客户端的单向数据流。非常适合用来推送状态更新、新闻源等。
从简单的 setInterval 到综合运用多种策略的智能轮询,再到最终选择 WebSockets 或 SSE,技术的演进体现了对性能、用户体验和系统健壮性的不懈追求。