fix(useRequest): 修复runAsync在节流状态下不生效的问题#2899
fix(useRequest): 修复runAsync在节流状态下不生效的问题#2899Arktomson wants to merge 2 commits intoalibaba:masterfrom
Conversation
- Changed runAsync binding from .bind() to arrow function for dynamic lookup - Added unit tests for runAsync with throttle configuration - Fixes alibaba#2898
commit: |
|
本地 patch 测试了下。节流虽然生效了,但是 2 个 promise 只有一个 resolve,另一个永远不会 resolve |
又是另外一个问题了0.0,改的话看上去就是节流期间多次请求的话返回同一个promise |
There was a problem hiding this comment.
Pull request overview
This PR fixes useRequest’s exported runAsync so it correctly respects throttle behavior by avoiding a .bind() that pins the pre-plugin runAsync implementation, and adds tests around throttled runAsync usage.
Changes:
- Update
useRequestImplementto exposerunAsyncvia an arrow function so it always calls the latestfetchInstance.runAsync(including plugin-wrapped versions). - Modify the throttle plugin’s
runAsyncwrapper to reuse a promise within the throttle window. - Add unit tests covering
runAsyncwith throttle settings, including the reportedleading: true / trailing: falsecase.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/hooks/src/useRequest/src/useRequestImplement.ts | Fixes runAsync export to dynamically call the (possibly plugin-wrapped) fetchInstance.runAsync. |
| packages/hooks/src/useRequest/src/plugins/useThrottlePlugin.ts | Updates throttled runAsync promise behavior; currently has trailing-semantics issues. |
| packages/hooks/src/useRequest/tests/useThrottlePlugin.spec.ts | Adds tests for throttled runAsync, but doesn’t cover throttleTrailing: true behaviors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fetchInstance.runAsync = (...args) => { | ||
| return new Promise((resolve, reject) => { | ||
| const now = Date.now(); | ||
|
|
||
| // If there's a current promise and it was created within the throttle window, | ||
| // return it to share the result | ||
| if (currentPromise && now - promiseCreatedAt < throttleWait) { | ||
| return currentPromise; | ||
| } |
There was a problem hiding this comment.
The currentPromise fast-path returns before calling throttledRef.current, which prevents lodash's throttle from registering subsequent calls. This breaks throttleTrailing: true (no trailing invocation) and throttleLeading: false (trailing uses the first call's args instead of the latest). Consider always invoking the throttled function on every call, while sharing a promise for the current throttle window, and ensure the executed callback uses the latest args for trailing behavior.
| // Clear current promise after a delay to allow trailing calls | ||
| setTimeout(() => { | ||
| if (currentPromise && Date.now() - promiseCreatedAt >= throttleWait) { | ||
| currentPromise = null; | ||
| } | ||
| }, 0); |
There was a problem hiding this comment.
The setTimeout(..., 0) blocks intended to clear currentPromise will almost never clear it because promiseCreatedAt was just set for this window, so Date.now() - promiseCreatedAt >= throttleWait will be false at +0ms. This logic is also duplicated in both then and catch. Either remove the clearing entirely (since the next window overwrites currentPromise), or clear it via a timer aligned to throttleWait / when the throttle window ends.
| test('useThrottlePlugin should respect throttleLeading and throttleTrailing options with runAsync', () => { | ||
| const callback = vi.fn(); | ||
|
|
||
| act(() => { | ||
| hook = setUp( | ||
| () => { | ||
| callback(); | ||
| return request({}); | ||
| }, | ||
| { | ||
| manual: true, | ||
| throttleWait: 3000, | ||
| throttleLeading: true, | ||
| throttleTrailing: false, | ||
| }, | ||
| ); |
There was a problem hiding this comment.
Test coverage only exercises throttleTrailing: false for runAsync. Given the plugin exposes both throttleLeading and throttleTrailing, add a case for throttleTrailing: true (and ideally throttleLeading: false) asserting that the trailing invocation runs and uses the latest call's params; this would catch regressions in trailing behavior.
|
这次修复 不过 问题在于:当节流窗口内再次调用 例如默认节流语义下(
所以这个改动虽然修复了 我建议:
这样能更稳地验证修复没有引入新的节流回归。 |
|
okk后续我修改完再说明一下
…---原始邮件---
发件人: ***@***.***>
发送时间: 2026年3月28日(周六) 晚上10:59
收件人: ***@***.***>;
抄送: "sheng ***@***.******@***.***>;
主题: Re: [alibaba/hooks] fix(useRequest): 修复runAsync在节流状态下不生效的问题 (PR #2899)
crazylxr left a comment (alibaba/hooks#2899)
这次修复 useRequestImplement.ts 里 runAsync 的绑定方式,我觉得方向是对的;.bind(fetchInstance) 确实会把 runAsync 固定到插件改写前的实现,导致节流插件无法生效。
不过 useThrottlePlugin.ts 里新增的 currentPromise 复用逻辑会引入新的行为回归,建议这里再看一下。
问题在于:当节流窗口内再次调用 runAsync 时,现在会直接 return currentPromise,不会再进入 throttledRef.current(...)。这样 lodash throttle 收不到这次调用,trailing 触发就被绕过去了。
例如默认节流语义下(leading: true, trailing: true):
runAsync(1) 立即执行
50ms 内再调用 runAsync(2)
预期应该在节流窗口结束后再触发一次 trailing 调用
但现在第二次调用直接复用旧 Promise,没有进入 throttle,最终只会执行第一次
所以这个改动虽然修复了 #2898 的场景,但会破坏 trailing: true(以及依赖“最后一次参数”的场景)。
我建议:
保留 useRequestImplement.ts 里把 runAsync 改成箭头函数转发的修复
不要在 throttle 窗口内直接短路掉对 throttledRef.current 的调用
另外补几个 runAsync 用例覆盖:
leading: true, trailing: true
leading: false, trailing: true
trailing 场景下是否使用最后一次调用参数
这样能更稳地验证修复没有引入新的节流回归。
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
🤔 这个变动的性质是?
🔗 相关 Issue
Fixes #2898
💡 需求背景和解决方案
问题描述:
当使用
useRequest配置节流选项(throttleLeading: true,throttleTrailing: false)时,runAsync方法不遵守节流配置,会发送所有请求;而run方法能够正常工作。问题根因:
问题由
useRequestImplement.ts中使用.bind()方法导致。当调用fetchInstance.runAsync.bind(fetchInstance)时,会创建一个新函数,该函数永久引用绑定时刻的原始runAsync方法。之后,当节流插件替换fetchInstance.runAsync为节流版本时,已绑定的函数仍然指向原始的未节流方法。解决方案:
将
runAsync的绑定方式从:改为: