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

Express服务中Promise无限循环异常行为原因排查

问题描述

现有如下Node.js服务脚本:

const got = require("got");
const getStream = require("get-stream");
const app = require("express")();

async function httpGet() {
  console.log("getting response");
  let targetUrl = "http://localhost:3000/api";
  let gotOptions = {
    method: "get",
    headers: { "content-type": "application/json" },
    body: undefined,
    retries: 0
  };
  let response = await new Promise(async (resolve, reject) => {
    let stream = got.stream(targetUrl, gotOptions);
    stream.on("error", async error => {
      try {
        resolve(stream);
      } catch (err) {
        reject(err);
      }
    });
    stream.on("response", async res => {
      try {
        resolve(await getStream(res));
      } catch (err) {
        reject(err);
      }
    })
  });
  return response;
}

async function apiMiddleware(req, res, next) {
  try {
    const response = await httpGet();
    console.log(response);
  } catch (e) {
    throw new Error("some error");
  }
  next();
}

app.use("/", [apiMiddleware]);

app.get("/api", (req, res) => {
  res.json({ data: "some output" });
});
app.get("/someapp", (req, res) => {
  res.end();
});

app.listen(3000, () => {
  console.log("listening on port 3000");
});

访问localhost:3000/someapp路径时,控制台无限循环输出如下日志:

getting response
getting response
getting response
...

调试时发现Promise执行逻辑总是跳转回httpGet()函数起始位置,需要定位异常原因。

异常根因

本质是自调用递归导致的请求死循环,由两处逻辑共同触发:

  • 中间件挂载范围过大
    app.use("/", [apiMiddleware])把apiMiddleware注册成了根路径全局中间件,服务收到的所有请求,无论目标路径是/someapp还是/api,都会先经过这个中间件处理,才会进入对应路由的处理逻辑。
  • 中间件逻辑触发自请求
    该中间件执行时会调用httpGet(),向服务自身监听的http://localhost:3000/api发GET请求。这个新发出的请求到达本地3000端口的服务后,会再次触发全局中间件执行,再次调用httpGet()发起新的请求,整个递归过程没有终止条件,就形成了无限循环,因此控制台会反复打印getting response。调试时看到逻辑跳回httpGet()起始位置,就是新一轮递归请求触发的函数重入。

另外代码里还有两个会加重异常的隐患,虽然不是死循环的直接诱因:

  • 给Promise构造函数传入async执行器属于典型反模式:执行器内部异步逻辑抛出的错误无法被Promise自动捕获,很容易出现吞错误、Promise状态游离的问题。
  • Express异步中间件的错误处理不符合规范:async函数内直接throw的错误不会被Express默认错误处理逻辑捕获,会导致请求一直挂起,不会返回错误响应打断递归链路。
修复思路
  • 收窄apiMiddleware的挂载范围,不要作为全局中间件拦截所有请求,比如只挂载到需要校验的业务路由上,确保httpGet发出的/api请求不会再次触发该中间件,从根源切断递归链路。
  • 修正Promise的写法,去掉多余的async执行器包装,同时给异步中间件加错误捕获逻辑,避免请求挂起。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:57:12