You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();

修复说明

  1. 服务端:
    • 监听数据文件变化,更新时主动通知所有挂起的请求
    • 只有无最新数据时才挂起请求,否则直接返回结果
    • 设置请求超时,避免连接长期占用资源
  2. 客户端:
    • 请求失败时添加延迟重试逻辑,降低异常场景下的请求频率
    • 成功响应后立刻发起下一次请求,维持长轮询的持续监听状态

内容的提问来源于stack exchange,提问作者Oluwafemi Olawale Durodola

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 09:25:25