🌪️ Promise永久Pending状态造成内存泄漏

阿轩的BUG
文章191
评论56
标签101
站点日志
作者总数:1位
置顶文章:0篇
标签总数:101条
文章总数:196篇
评论总数:56条
微语总数:201条
运行天数:2315天
最近更新:2026-07-28
文章标签
最新评论
Sherlock
2025-08-21
不是应该打包成apk文件吗,怎么突然就运行编译了,下一步呢
老金er
2025-05-25
人生设计非常必要,在信息化发达的今天,周围大部分人依然只能过着人生工程模式生活,长时间生活洗礼后,尽管“人生工程”已经不再适合很多人,他们也无法做出任何改变,人生设计给我们提供了新的思路,值得尝试
赏帮赚
2025-05-25
职场太多的尔虞我诈
三笑
2024-08-16
emlog6.0.1可以升级支持吗?
pony
2024-07-08
这篇文章提供了一种新的人生规划方法——"人生设计",它借鉴了产品设计的理念,鼓励人们以创造性和探索性的方式思考和规划自己的生活。以下是对这篇文章的几个评价维度: 创新性:将产品设计的理念应用于人生规划是一个新颖的视角,为人们提供了一种跳出传统生活轨迹,探索个性化生活路径的方法。 实用性:文章不仅提出了理念,还详细介绍了具体的实施步骤,包括自我评估、制定计划、原型设计和实践选择等,具有很强的操作性。 启发性:通过强调好奇心、不断尝试、重新定义问题等心态,文章鼓励人们打破常规,勇于探索未知,这对于激发个人潜能和创造力具有积极作用。 适应性:"人生设计"理念适用于不同人生阶段和不同背景的人,无论是希望从头开始的人,还是已经取得一定成就但希望探索新方向的人,都可以从中获得启发。 情感共鸣:文章对许多人对传统生活轨迹的不满和对改变的渴望进行了深刻的洞察,能够引起读者的共鸣。 可持续性:人生设计强调的是一个持续迭代和试错的过程,这与现实生活的发展规律相吻合,有助于人们在不断变化的环境中做出适应性调整。 教育意义:作为一种人生规划方法,"人生设计"可以作为教育的一部分,帮助年轻人更早地学会自我探索和规划,为未来的生活和职业发展打下基础。 局限性:尽管"人生设计"提供了一种新的思考框架,但它可能需要个人具备一定的自我认知和反思能力,对于一些缺乏自我探索经验的人来说,可能需要额外的指导和支持。 总体来说,这篇文章提供了一种有价值的人生规划方法,对于那些渴望改变、寻求个性化生活的人来说,是一种有益的参考和指导。
首页值得一看 🔍️

摘要

若**promise**一直保持pending状态 ,将会在内存中保存相应的上下文,无法释放,这可能导致内存泄漏

尽管调用promise的 react 组件已经销毁,由于promise的状态未更新,导致保存React组件上下文不会释放 ,造成内存占用。

通过Promise.race设置超时的方式并不会解决Promise长时间pending占用内存的问题, Promise不会取消,直到它更改为fullfilled或者rejected状态

应避免写出Promise永远处于pending状态的代码。

本文将会用简单的几个demo来看下内存泄漏的表现,避免在业务中意外写出泄漏的代码。

应避免Promise永久pending状态

React组件销毁后,Promise仍然pending状态

尽管**React**组件已经销毁,但是其调用的promise仍然是pending状态,将组件的上下文保存在内存中,不会释放, 直到promise的状态修改为fullfilled或者rejected。

复现demo

通过下面这个例子,我们发现

  • 即使在Parent组件销毁后,aPromise返回的promise对象和Parent组件的上下文仍然保存在内存中;

  • 直到10min后,promise的状态修改为fullfilled后,控制台打印test,内存中的promise对象和parent组件的上下文被释放。

import { useEffect, useState } from 'react';

const Parent = () => {
  const [data, setData] = useState('this is a test for memory');
  const aPromise = async () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('');
      }, 600000); // 10min
    });
  };

  useEffect(() => {
    aPromise().then(() => {
      console.log('test');
      setData('this is a test for update for memory leak');
    });
  }, []);
  return <div>{data}</div>;
};
export const CasePromisePending = () => {
  const [show, setShow] = useState(true);
  return (
    <div>A
      <div>测试promise pending内存泄漏</div>
      <button onClick={() => setShow(!show)}>click</button>
      {show && <Parent></Parent>}
    </div>
  );
}

复现步骤:Allocation instrumentation on timeline

react**组件已经卸载,该泄漏不会导致**dom的泄漏,无法通过内存快照寻找泄漏的dom元素,需要利用Allocation instrumentation on timeline查看内存中的对象。

Allocation instrumentation on timeline:录制一段时间内的javascipt内存分配情况,可以查看到录制的时间结束时为止,内存中仍然存留的对象

  1. 第一步:F12打开控制台,选择Memory选项中的Allocation instrumentation on timeline

image

  1. 第二步:点击recording按钮,开始记录内存分配状态,不断点击click按钮,查看内存的变化

image.png

内存排查:大量的Promise对象和React组件的上下文被保存在内存中

排查捕捉到的Promise对象和Object对象发现:

发现1: 36个Promise对象内存未释放和26个Object对象;

查看Promise对象中,有大量的我们定义的仍然处于pending状态的Promise;通过文件路径可以定位到具体的代码。

image

内存中的promise

发现2: 内存中26个Object对象未释放

image

image

内存中的Object对象(组件的上下文)

image

发现3: promise状态修改后,promise内存和组件内存释放

promise状态修改为fullfilled或者rejected后,promise内存和组件内存释放

10min后,控制台打印了test,再次重复上面的复现步骤发现:(点击了两次click按钮)Promise对象只有4个,Object对象只有16个(其中部分不相关的对象)

image

Promise关联的react组件中使用了ref引用,可能造成dom泄漏

复现demo

在promise所在组件的父组件中使用useRef,当promise一直未修改pending状态时,将出现**dom**内存泄漏

import { useEffect, useRef, useState } from 'react';

const Parent = (props) => {
  const [data, setData] = useState('this is a test for memory');
  const aPromise = async () => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('');
      }, 500000);
    });
  };
  useEffect(() => {
    aPromise().then((value) => {
      console.log('test');
      setData('this is a test for update for memory leak');
    });
  }, []);
  return (
    <div>
      {data}
    </div>
  );
};
const Child = () => {
  const ref = useRef(null);
  return (
    <div ref={ref}>
      <Parent target={ref}></Parent>
    </div>
  );
};
const CasePromisePendingRef = () => {
  const [show, setShow] = useState(true);
  return (
    <div>
      测试promise pending内存泄漏
      <button onClick={() => setShow(!show)}>click</button>
      {show && <Child></Child>}
    </div>
  );
};
export { CasePromisePendingRef };

复现步骤:heap snapshot内存快照记录dom泄漏

  1. 第一步:F12打开控制台,选择Memory选项中的Heap snapshot

image

  1. 第二步:点击recording按钮,记录内存快照,多次点击click按钮,再次记录内存快照,对比两次内存快照的内存变化

image.png

内存排查:发现18个游离的HTMLDivElement元素

发现1: 游离的promise相关的dom数量和点击次数相关

image

发现2: 内存中保存了react组件的状态

image

发现3:promise状态修改后,promise内存和组件内存释放(全局promise仍然可以被回收)

在promise状态修改为fullfilled和rejected后,对比一开始的内存快照,发现游离的dom已经消失。

若promise状态一直为pending状态,则会出现内存泄漏。

image

【全局promise】执行结束之后ref中游离的dom detach不会被垃圾回收

解决方案

利用Promise.race设置超时能够解决吗?

不能!

Promise.race只是将最先返回的结果作为Promise.race的返回值,但并不会取消超时未返回的promise.

未返回的promise仍然在异步队列中等到结果的返回,过程中,对组件仍然引用。

可以在控制台中做如下例子验证:

const a = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("a");
        resolve("a")
    }, 50)
})
const b = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log("b");
        resolve("b")
    }, 10000)
})
Promise.race([a(), b()]);

可以发现10s后,控制台中仍然会打印b。所以超时未返回的promise仍然会对react组件有引用。

避免Promise一直pending

Promise一旦创建是无法取消的,本质上,Promise是无法被终止的。它永远会等待结果的返回。

需要我们自己保证promise并不会一直pending,导致内存无法释放

排查内存的工具?

Chrome Memory Timeline

利用该工具可以捕捉一段时间的内存分配情况,截止到录制结束时间为止,每个时刻内存的占用情况,以及相应的占用内存的对象,也可以捕捉到游离(detach)的dom等元素

image]

Chrome Memory Heap Snapshot

利用该工具可以捕捉某个时刻的内存,可以将两个时刻的内存进行对比,发现两个阶段增加的游离的dom,以此排查内存的泄漏情况。

image

image

Performance Monitor

可以实时观测内存的变化情况,主要观察DOM Nodes和JS heap size;

如果组件比较小,JS heap size的数据粒度比较小,观测比较困难;

  1. 利用performance.memory.usedJSHeapSize来看具体的内存数据(有一定的波动范围)
  1. 增加组件内存粒度:批量设置较大组件进行测试,比如Array.from({length: 10000}, (_, i) => i).map(i => <div></div>)

image

上一篇
⚡ 前端开发者应该养成的开发好习惯
下一篇
💫 为什么 JavaScript 的 parseInt(0.0000005) 打印“5”?