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

Node.js中https.get的timeout机制及1秒超时重试实现问题

Node.js HTTP 请求超时与重试问题排查及解决

问题背景

我正在用https.get发送GET请求,想明确timeout选项的工作机制。查资料得知该选项仅控制连接超时,套接字建立后便不再生效。

我编写了带重试逻辑的getRequest函数,既在请求选项中设置timeout:1000,又调用.setTimeout(1000)试图处理连接后的超时场景:

function getRequest(url) {
  return new Promise((resolve, reject) => {
    const req = https.get(url, {timeout: 1000}, res => {
      let rawData = '';
      res.on('data', chunk => {
        rawData += chunk;
      });
      res.on('end', () => {
        resolve(rawData);
      });
    }).setTimeout(1000);
    req.on('error', err => {
      reject(new Error(err))
    })
    req.on('timeout', () => {
      req.end();
      getRequest(url);
    })
  });
}

测试发现,100次请求中至少有一次耗时恰好5秒,推测是某个默认配置导致。我需要确保连接在1秒后立即关闭并发起重试。另外尝试node-fetch、axios等带超时功能的模块时,也遇到类似问题:Axios会抛出超时错误,但Node.js应用要等待5秒才结束。

问题根源

这个5秒延迟来自Node.js底层的套接字默认超时(socket.setTimeout()默认值为5000ms)。即便给请求设置了1秒超时,当套接字已建立但服务器迟迟不返回数据时,底层套接字的默认5秒超时才会触发,导致整个请求拖到5秒才结束。

同时你的重试逻辑存在问题:在timeout事件中直接调用getRequest(url),但递归调用的Promise未与外层Promise关联,外层Promise会一直处于pending状态,直到底层套接字超时才会有结果。

解决方案

1. 覆盖套接字默认超时

在请求的socket事件触发时,手动设置套接字超时时间,替换默认的5秒:

function getRequest(url) {
  return new Promise((resolve, reject) => {
    const req = https.get(url, { timeout: 1000 }, res => {
      let rawData = '';
      res.on('data', chunk => {
        rawData += chunk;
      });
      res.on('end', () => {
        resolve(rawData);
      });
    });

    // 覆盖套接字默认超时
    req.on('socket', (socket) => {
      socket.setTimeout(1000);
      socket.on('timeout', () => {
        req.destroy(new Error('Socket timeout'));
      });
    });

    req.on('timeout', () => {
      req.destroy(new Error('Request timeout'));
    });

    req.on('error', err => {
      // 将重试的Promise结果关联到外层Promise
      resolve(getRequest(url));
    });
  });
}

2. 正确关联重试Promise

将重试调用的getRequest(url)通过resolve()传递给外层Promise,避免外层Promise一直挂起。

3. 用destroy()替代end()

req.end()仅结束请求但不会强制关闭套接字,改用req.destroy()可立即终止连接,避免等待底层套接字的默认超时。

4. 限制重试次数(可选)

为避免无限重试,可添加重试次数参数:

function getRequest(url, retries = 3) {
  return new Promise((resolve, reject) => {
    const req = https.get(url, { timeout: 1000 }, res => {
      let rawData = '';
      res.on('data', chunk => {
        rawData += chunk;
      });
      res.on('end', () => {
        resolve(rawData);
      });
    });

    req.on('socket', (socket) => {
      socket.setTimeout(1000);
      socket.on('timeout', () => {
        req.destroy(new Error('Socket timeout'));
      });
    });

    req.on('timeout', () => {
      req.destroy(new Error('Request timeout'));
    });

    req.on('error', err => {
      if (retries > 0) {
        resolve(getRequest(url, retries - 1));
      } else {
        reject(new Error(`重试${3 - retries}次后失败: ${err.message}`));
      }
    });
  });
}

关键说明

  • https.get的timeout选项仅负责连接阶段的超时(DNS解析、TCP握手),连接建立后失效。
  • 必须通过socket事件设置套接字超时,才能覆盖默认5秒,确保数据传输阶段也能在1秒超时。
  • destroy()会立即关闭连接并触发error事件,让重试逻辑及时执行,无需等待底层5秒超时。

内容的提问来源于stack exchange,提问作者macaw2980

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:03:32