背景是这样,这段代码就是 token 过期前 2 分钟刷新下 token ,但是它会在我切换到其他网页之后(这个刷新 token 的网页不关闭,后台运行),过很长一段时间在切回来就可能爆内存(可能 12 小时,可能 24 小时?),但是时间太短不会爆内存,所以我一直没找到很好的办法去测试定位
timer 是 rxjs 的定时器操作符
export function RefreshTokenComp({
refreshTokenActor,
}: {
refreshTokenActor: RequestActor<{
refreshToken: string;
}>;
}) {
const [access$, updateAccess] = useAccessMgr();
const access = useObservable(access$) || {};
const { refresh_token, expireAt } = access;
const [refreshTokenRequest] = useRequest(refreshTokenActor, {
onSuccess({ arg }) {
updateAccess(fromOAuthToken(arg.data));
},
});
useEffect(() => {
if (!refresh_token) return;
const expiresIn = moment(expireAt).diff(moment(), "s") - 120; // token 过期前二分钟左右刷新
const sub = timer(expiresIn * 1000).subscribe(() => {
refreshTokenRequest({ refreshToken: refresh_token });
});
return () => {
sub.unsubscribe();
};
}, [refresh_token]);
return null;
}
1
wpyfawkes 172 天前
我不是太懂 JavaScript,以下是 Claude 的回复
函数实现 函数实现看起来是正确的。它使用了 useEffect 来处理 token 的刷新,这是 React 中常见的做法。然而,这段代码可能存在潜在的内存泄漏问题。在 useEffect 的清理函数中,我们取消了定时器的订阅,但是如果 refresh_token 在定时器触发之前改变,那么定时器可能会被意外地触发两次。我们应该在 useEffect 的依赖数组中添加 expiresIn 。 |
2
Laobai 172 天前
大概率是定时器创建了,没有被销毁
|
3
rookie2luochao OP @Laobai 肯定,我就是不知道怎么样导致它没有销毁,我以为写了 sub.unsubscribe() 就算销毁了
|
4
rookie2luochao OP @wpyfawkes 哈哈哈,这个是收费的吗,我去试试按这个建议改进一下呢
|
5
Brain777 172 天前
const sub = timer(expiresIn * 1000).subscribe(() => {
refreshTokenRequest({ refreshToken: refresh_token }); // 感觉应该需要在刷新之后清理定时器订阅 sub.unsubscribe(); }); |
6
rookie2luochao OP @wpyfawkes 应该是 expireAt ,这个假 AI😂
|
7
rookie2luochao OP @Brain777 从代码上面看我觉得可以不用,但是像你说的刷新之后多清除一下,能避免一些想不到的情况
|
8
lisxour 172 天前
|
9
rabbbit 172 天前
没看出啥问题,timer 换成 setTimeout 试试还会有问题吗?
|
10
renmu 172 天前 via Android
我猜是切换到其他标签页后,页面的定时器没有再执行,当你切回来后,页面没有执行的定时器一起被执行了,然后爆了
|
11
ZENGQH 172 天前
切换标签后 导致的计时器异常,有个属性可以判断是否为当前便签页
|
12
vanchKong 172 天前
以我浅薄的 rn 开发经历,我是这么写的:
useEffect(() => { let timer = setInterval(() => { ... }, 2000) return () => { clearInterval(timer) } }, []) 不知道是不是我没用懂 react |
13
yuuko 172 天前
没看出啥问题,找到问题后能踢我一脚吗
|
14
XV5V7stzN1ns0TPL 172 天前
在 JavaScript 应用程序中,内存泄漏可能由多种原因引起,特别是当使用异步操作和事件监听器时。在你提供的代码片段中,有几个可能导致内存泄漏的潜在因素:
1. **定时器订阅未正确清理**:代码中使用了 RxJS 的 `timer` 创建了一个定时器,并订阅了它。在组件卸载时,通过调用 `sub.unsubscribe()` 来取消订阅,这是正确的做法。然而,如果组件卸载不完全或者 `unsubscribe` 调用时机不当,订阅可能不会被正确清理。 2. **闭包陷阱**:如果 `useEffect` 的回调函数或其返回的清理函数形成了闭包,它们可能会捕获并保留组件的状态,即使组件不再渲染。 3. **组件状态未清除**:如果组件状态(如 `access`)在组件卸载后仍然被某些引用或事件监听器保留,这可能会导致内存泄漏。 4. **全局事件监听器**:如果代码中有全局事件监听器,并且这些监听器引用了组件的状态或回调,它们可能会阻止组件被垃圾回收。 5. **异步更新队列**:JavaScript 的异步更新队列可能在组件卸载后仍然尝试更新组件状态,导致内存泄漏。 要解决这个问题,你可以尝试以下方法: - **确保组件卸载**:使用 React DevTools 或类似工具检查组件是否真的被卸载了。 - **使用条件渲染**:只在需要时渲染组件,避免不必要的挂载和卸载。 - **优化状态管理**:避免在组件状态中存储大量数据或长生命周期的对象。 - **使用 WeakMap 或 WeakRef**:如果需要存储对组件的引用,使用 `WeakMap` 或 `WeakRef` 可以减少内存泄漏的风险。 - **避免长生命周期的闭包**:确保闭包不会捕获不必要的组件状态。 - **手动管理订阅**:如果可能,手动管理 RxJS 订阅的生命周期,确保在组件卸载前取消所有订阅。 - **测试和调试**:使用内存泄漏检测工具,如 Chrome DevTools 的 Heap Snapshot 比较,来识别和定位内存泄漏。 - **代码审查**:对相关代码进行彻底的审查,查找可能导致内存泄漏的逻辑错误。 最后,为了更好地测试和定位内存泄漏,你可以尝试以下方法: - **自动化测试**:编写自动化测试来模拟长时间运行的场景。 - **性能监控**:在开发环境中使用内存分析工具,如 React DevTools 或其他 JavaScript 性能监控工具。 - **模拟长时间运行**:在测试环境中模拟长时间运行的场景,比如使用循环或定时器来频繁触发组件的挂载和卸载。 - **逐步调试**:逐步执行代码,检查在特定操作后内存的使用情况。 通过这些方法,你应该能够更准确地定位和解决内存泄漏问题。 |
15
forbreak 172 天前
大概就是切换 tab 之后,浏览器休眠了。切回来之后积攒的那些一起执行就爆掉了。 解决方案: 监控下切换标签的事件,切走的时候就别订阅了。切回来的处理一次 token 刷新。
|
18
superfatboy 172 天前
#5 感觉不用,正常情况下,refresh_token 更新,sub.unsubscribe()肯定会 执行一次
|
19
googleaccount 172 天前
```js
export function RefreshTokenComp({ refreshTokenActor, }: { refreshTokenActor: RequestActor<{ refreshToken: string; }>; }) { const [access$, updateAccess] = useAccessMgr(); const access = useObservable(access$) || {}; const { refresh_token, expireAt } = access; let sub:Timer const [refreshTokenRequest] = useRequest(refreshTokenActor, { onSuccess({ arg }) { updateAccess(fromOAuthToken(arg.data)); }, }); useEffect(() => { if (!refresh_token) return; if (sub) sub.unsubscribe(); const expiresIn = moment(expireAt).diff(moment(), "s") - 120; // token 过期前二分钟左右刷新 sub = timer(expiresIn * 1000).subscribe(() => { refreshTokenRequest({ refreshToken: refresh_token }); }); return () => { sub.unsubscribe(); }; }, [refresh_token]); return null; } ``` 试试这样 你这个 refresh_token 每隔两分钟就会变 说明 useEffect 里面的定时器每隔两分钟就会执行一次 执行多了不就爆了没,每次执行前清空一下就好了 |
20
googleaccount 172 天前
我没测试上面这些代码,还一个简单粗暴的办法就是把定时器存在 ref 上 就不会出现这些问题了。
```js const subscriptionRef = useRef(null); useEffeect(() => { ... if (subscriptionRef.current) { subscriptionRef.current.unsubscribe(); } ... subscriptionRef.current = timer() ... }, [refresh_token]); return () => { subscriptionRef.current.unsubscribe(); }; }) ``` |
21
googleaccount 172 天前
@googleaccount 咋格式呢
```javascript const subscriptionRef = useRef(null); useEffeect(() => { ... if (subscriptionRef.current) { subscriptionRef.current.unsubscribe(); } ... subscriptionRef.current = timer() ... return () => { subscriptionRef.current.unsubscribe(); }; }, [refresh_token]); ``` |
22
leokun 171 天前
这段代码不会出现内存泄漏
大家不妨设想一下 refresh_token, expireAt 每秒都在改变,这里也不会内存泄漏,内存泄漏应该在别处 |
23
IvanLi127 171 天前
改成每秒刷一次看看内存会不会快速上涨吧,单看这代码没看出太大问题
|
24
rookie2luochao OP @yuuko 待我好好定位一下,搞清楚了踢你
|
25
rookie2luochao OP @vanchKong 我们两这写法思路一样,就是我比你多了 deps 依赖
|
26
rookie2luochao OP @rabbbit 问题应该不在 timer 上,应该是 v 友说的还是在 timer 没清除上面找原因
|
27
rookie2luochao OP @lisxour 好的,你的截图看不到,我觉得也是需要学习一下分析工具了
|
28
rookie2luochao OP @renmu 你说的这个原理我也想过,这算正常机制呢,还是说需要怎么去避免
|
29
rookie2luochao OP @ZENGQH 所以我需要通过是否离开标签页做清除吗?会不会麻烦了
|
30
rookie2luochao OP @googleaccount 换到 ref 上面,确实可能更靠谱
|
31
rookie2luochao OP @leokun 内存泄漏大概率就是这里,我也是直观上看不出来代码有问题,主要还是应该对 react effect 的执行机制没有理解透彻,同事也没看出来,只能说把 deps 写全,我只写一个 token 依赖是因为 token 和 expireAt 是同步变的,所以我就只写了 token 这个依赖,然后用 ref 引用,然后多清除几次 unsubscribe ,但是这只是尝试的解决办法
|
32
vanchKong 171 天前
@rookie2luochao 另外,我想说,可以试一下将 timer 用 useState 去定义,我开发 rn 的时候,碰到一种情况,就是 timer 不是用 useState 定义的,然后直接 clearInterval ,但是 timer 依然在执行的情况。
|
33
rookie2luochao OP @vanchKong 嗯嗯,谢谢哈,像 V 友说的用 useRef 更靠谱一点,自己还是需要找到能定位这种内存泄漏的方法,虽然目前我的产品定位内存泄漏时间线,现在有解决办法了,我看不能找到一个简单方法去定位
|
34
realJamespond 171 天前
rxjs 初始化在 effect 中不应该加依赖项,依赖项应该用 ref 保存在 rxjs 中引用
|