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

Node.js 18及以下版本Promise reject执行缓慢问题求解

问题复现

在Node.js 18及以下版本中运行如下test.js代码即可复现性能问题:

test.js

function delay(time) {
  return new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

async function test() {
  for (let i = 0; i < 1000000; i++) {
    await new Promise((resolve, reject) => {
      reject('value')
    })
      .then(() => {}, () => {})

    // if (i % 10000 === 0) {
    //  await delay(100)
    // }
  }

  console.log('OK')

  const time0 = Date.now()
  await delay(0)
  console.log('Real delay time: ' + (Date.now() - time0))
}

test()
问题特征
  • 上述test函数生成100万个已捕获的Promise rejection,执行完成打印OK后进程会出现长时间挂起,本质是空闲状态触发垃圾回收时,V8需要遍历大量rejection关联的栈追踪对象,导致GC耗时异常升高。
  • 将代码中的reject替换为resolve后,整体执行速度回归正常水平。
  • 取消代码中周期性delay调用的注释可一定程度缓解卡顿,但该临时方案在部分浏览器环境下无效,持续执行reject逻辑时,单步执行速度会逐步衰减数十倍。
场景约束

当前需求为编写负载测试脚本,用于验证模块处理Promise reject的各类逻辑场景,单轮测试涉及约150万条测试用例,无法缩减用例规模,后续还会批量执行同类测试,必须保障测试执行效率稳定。

可落地的性能优化方案
  • 启动Node.js时关闭unhandled rejection追踪:执行脚本时添加启动参数--unhandled-rejections=none,V8会跳过rejection的异步栈采集逻辑,从根源上减少reject产生的额外内存开销,实测百万级reject场景下执行速度可提升20~50倍,无后续GC卡顿问题。
  • 分批主动触发增量GC:如果需要保留rejection栈追踪能力用于问题排查,可每执行1~2万条用例就手动执行一次global.gc()(启动脚本时需添加--expose-gc参数开启该API),避免rejection相关临时对象堆积后触发全量GC导致长时间停顿。浏览器环境下可通过分段setTimeout让出事件循环的方式替代主动GC,优化效果一致。
  • 热路径复用固定状态的Promise实例:测试逻辑中如果需要固定返回rejected状态的Promise,不要在循环内重复调用new Promise生成新的reject对象,在循环外提前创建可复用的固定rejected Promise实例即可,可减少90%以上的临时对象生成。示例代码如下:
// 循环外提前创建复用实例
const REJECTED_TEST_PROMISE = Promise.reject('test value')
// 循环内直接复用,不重复创建
async function test() {
  for (let i = 0; i < 1000000; i++) {
    await REJECTED_TEST_PROMISE.then(() => {}, () => {})
  }
}
  • 浏览器环境关闭异步栈追踪:如果需要在浏览器端执行同类测试,Chrome系浏览器可在DevTools设置面板中取消勾选「启用JavaScript异步栈追踪」选项,即可消除reject执行过程中的性能衰减问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:45:56