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

为何这段含Promise.race的JavaScript代码会出现内存泄漏?

为什么这段Promise.race代码会出现内存泄漏?

问题重现

以下代码在Node.js 20.10.0中运行时,会触发JavaScript heap out of memory错误:

// ephemeralLeak.js

async function promiseValue(value) {
  return value;
}

async function run() {
  for (;;) {
    await Promise.race([promiseValue("foo"), promiseValue("bar")]);
  }
}

run();

运行命令:

node --max-old-space-size=512 test/examples/ephemeralLeak.js

作为对比,使用setImmediate异步resolve的版本则无内存泄漏:

// setImmediateNoLeak.js

function promiseValue(value) {
  return new Promise((resolve) => {
    setImmediate(() => resolve(value));
  });
}

async function run() {
  for (;;) {
    await Promise.race([promiseValue("foo"), promiseValue("bar")]);
  }
}

run();

原因分析

核心问题出在同步resolved的Promise(立即处于fulfilled状态的Promise)的V8引擎处理机制上:

  • 当promiseValue直接返回值时,async函数会将其包装为一个立即fulfilled的Promise。V8对这类Promise有特殊优化,会尽量同步处理其then回调。
  • 在无限循环中,每次await Promise.race(...)都会为两个立即fulfilled的Promise添加then订阅者。由于循环在微任务队列处理过程中立即重启,V8的垃圾回收机制无法及时清理这些临时Promise及其关联的回调链——这些对象的引用会被微任务队列的内部结构暂时持有,随着循环迭代不断累积,最终耗尽堆内存。
  • 而setImmediate版本中,Promise的resolve操作被推迟到宏任务队列执行。每次await会等待宏任务完成后再进入下一次循环,此时上一轮的Promise及其回调已被完全处理,无残留引用,因此可以被正常垃圾回收。

修复方案

除了setImmediate,更轻量的方案是让Promise的resolve操作延迟到当前事件循环的微任务阶段,避免触发V8对立即fulfilled Promise的同步优化:

// fixedVersion.js

function promiseValue(value) {
  return Promise.resolve().then(() => value);
}

async function run() {
  for (;;) {
    await Promise.race([promiseValue("foo"), promiseValue("bar")]);
  }
}

run();

也可以使用Node.js原生的process.nextTick实现类似效果:

function promiseValue(value) {
  return new Promise(resolve => process.nextTick(() => resolve(value)));
}

这种处理方式让Promise不会在创建时立即进入fulfilled状态,而是等待微任务队列执行时才resolve,确保每次循环的await能等待微任务处理完成,上一轮的Promise及其关联回调会被及时回收,不会累积内存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 13:32:49