Node.js与JavaScript长轮询优化:解决频繁请求及浏览器冻结问题
长轮询实现错误分析与修复方案
核心问题分析
服务端错误
- 未实现长轮询核心逻辑:挂起请求直到数据更新。当前代码收到
/datas请求后立刻读取文件并返回响应,本质是普通HTTP短连接,和长轮询无关。 subscribers集合仅存储响应对象但未实际用于数据更新通知,完全没有发挥作用。
客户端错误
- 请求完成后(无论成功/失败)立刻递归调用
fetchTickerValue,导致请求无间隔连续发送,大量请求挤占浏览器主线程资源,最终引发冻结。 - 未设置失败重试间隔,异常场景下会持续高频发起请求,加重资源消耗。
修复后的代码实现
服务端(server.js)
添加数据更新监听,实现请求挂起与主动通知逻辑,同时设置请求超时避免连接长期占用:
const { createServer } = require('http'); const { parse } = require('url'); const fs = require('fs'); const path = require('path'); const PORT = 3000; let subscribers = Object.create(null); let lastData = null; const dataPath = path.join(process.cwd(), 'data', 'datas.json'); // 加载数据并通知订阅者 function loadAndNotifyData() { fs.readFile(dataPath, 'utf-8', (err, data) => { if (!err) { lastData = data; // 数据更新时通知所有挂起的请求 Object.values(subscribers).forEach(res => res.end(lastData)); subscribers = Object.create(null); // 清空订阅列表 } }); } // 监听数据文件变化 fs.watch(dataPath, (eventType) => { if (eventType === 'change') loadAndNotifyData(); }); // 初始化加载数据 loadAndNotifyData(); const server = createServer((req, res) => { const parsedUrl = parse(req.url, true); const urlPath = parsedUrl.pathname; const method = req.method.toLowerCase(); if (urlPath === "/" && method === "get") { res.writeHead(200, { "Content-Type": "text/html", "Access-Control-Allow-Origin": "*", }); res.end(`<h1>Home Page of the API</h1>`); return; } if (urlPath === "/datas" && method === "get") { res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Cache-Control": "no-cache, must-revalidate", "Connection": "keep-alive" }); // 已有最新数据则直接返回 if (lastData) { res.end(lastData); return; } // 无最新数据则挂起请求,加入订阅列表 const id = Math.random(); subscribers[id] = res; // 设置30秒超时,避免连接永久挂起 const timeout = setTimeout(() => { delete subscribers[id]; res.end(JSON.stringify([])); }, 30000); // 请求关闭时清理订阅与超时 req.on("close", () => { clearTimeout(timeout); delete subscribers[id]; }); return; } // 404处理 res.writeHead(404); res.end('Not Found'); }); server.listen(PORT, () => { console.log(`Server up and running,\nYou can now visit http://localhost:3000`); });
客户端(app.js)
添加失败重试延迟,避免无间隔请求挤占资源:
const prices = document.querySelector(".prices"); function createEl(resultDatas) { prices.innerHTML = ""; resultDatas.forEach((resultData) => { const div = document.createElement("div"); div.classList.add("element-container"); const p = document.createElement("p"); p.innerHTML = resultData.title; if (resultData.title.toLowerCase() === "silver") { p.classList.add(`silver`); } p.classList.add(`title`); div.appendChild(p); const h3 = document.createElement("h3"); h3.innerHTML = resultData.price; h3.classList.add("price"); div.appendChild(h3); prices.append(div); }); } const fetchTickerValue = async () => { try { const response = await fetch("http://localhost:3000/datas"); const data = await response.json(); createEl(data); } catch (error) { console.error(error?.message ?? "ticker call failed"); // 请求失败时延迟2秒重试 await new Promise(resolve => setTimeout(resolve, 2000)); } finally { // 成功响应后立刻发起下一次长轮询 fetchTickerValue(); } }; // 启动长轮询 fetchTickerValue();
修复说明
- 服务端:
- 监听数据文件变化,更新时主动通知所有挂起的请求
- 只有无最新数据时才挂起请求,否则直接返回结果
- 设置请求超时,避免连接长期占用资源
- 客户端:
- 请求失败时添加延迟重试逻辑,降低异常场景下的请求频率
- 成功响应后立刻发起下一次请求,维持长轮询的持续监听状态
内容的提问来源于stack exchange,提问作者Oluwafemi Olawale Durodola
相关产品推荐
相关产品推荐

