如何为支持指定次数递归重试的Promise实现节流?
我正在编写一个供React组件调用的Promise,用lodash的节流功能限制请求频率,避免组件频繁调用触发过多请求。现有实现代码如下,同时有几个疑问:
- api()调用前是否需要添加return?
- catch分支中调用retryableFn()前是否需要添加return?
- 如何正确使用节流后的函数实例?
- 如何正确进行try/catch异常捕获?
- 该节流后的函数是否支持then链式调用?
let throttledApi = throttle(() => { // tracking number of attempts let attempts = 0; const retryableFn = () => { // do I need a return before api here? api() .then((response) => { return response; }) .catch((error) => { attempts++; if (attempts === 3) throw error; // throw to the component using this functino // same question as above for below line else retryableFn(); }); }; }, 60 * 1000); // 1 minute // I am using it like this throttledFn = throttledApi(); // How do I properly use the throttled instance // How do I try/catch properly. // Is this thenable? throttledFn();
修正后的完整代码
import { throttle } from 'lodash'; // 节流后的API函数,返回Promise const throttledApi = throttle(() => { let attempts = 0; const retryableFn = () => { // 必须return api(),传递Promise给外部 return api() .then((response) => response) .catch((error) => { attempts++; if (attempts === 3) { // 用Promise.reject抛出错误,符合Promise链式规范 return Promise.reject(error); } else { // 必须return重试的Promise,纳入当前Promise链 return retryableFn(); } }); }; // 返回重试函数执行后的Promise return retryableFn(); }, 60 * 1000); // 1分钟节流间隔 // React组件中使用示例 // 方式1:async/await + try/catch const fetchData = async () => { try { const response = await throttledApi(); // 处理成功响应 console.log(response); } catch (error) { // 处理所有错误(含3次重试失败) console.error('请求失败:', error); } }; // 方式2:then/catch链式调用 const fetchData2 = () => { throttledApi() .then((response) => console.log(response)) .catch((error) => console.error('请求失败:', error)); };
疑问解答
1. api()调用前是否需要添加return?
需要。api()返回的是Promise,只有通过return把这个Promise传递出去,外部才能通过.then()或await获取请求结果。原代码没有return,外部无法拿到Promise,自然无法处理响应或错误。
2. catch分支中调用retryableFn()前是否需要添加return?
需要。重试时retryableFn()会返回新的Promise,必须通过return把这个Promise加入当前Promise链,这样外部的.catch()才能捕获重试过程中的错误,或在重试成功后拿到结果。如果不加return,重试的Promise会脱离当前链,外部无法感知重试结果。
另外建议把throw error改成return Promise.reject(error),更符合Promise链式写法,避免在catch中抛出错误导致未捕获异常。
3. 如何正确使用节流后的函数实例?
lodash的throttle会直接返回一个节流后的函数,你不需要执行throttledApi()去获取实例。原代码中throttledFn = throttledApi()是错误的,这相当于立即执行了一次节流函数,然后把无意义的返回值存到了throttledFn。
正确用法是直接把throttledApi作为节流后的函数,组件需要调用时直接执行throttledApi()即可。
4. 如何正确进行try/catch异常捕获?
有两种常用方式:
- async/await + try/catch:把调用节流函数的逻辑放在async函数里,用try包裹调用代码,catch捕获所有请求、重试过程中的错误。
- Promise链式调用的.catch():直接在
throttledApi()后链式调用.catch(),统一处理所有异常。
两种方式都能覆盖请求全流程的错误场景。
5. 该节流后的函数是否支持then链式调用?
修正后的版本完全支持。只要确保节流后的函数返回Promise(也就是我们在代码中return了retryableFn()的结果,而retryableFn()又return了api()的Promise链),外部就可以像使用普通Promise一样进行.then()链式调用。原代码不支持是因为没有返回任何Promise,修正后即可正常使用。
内容的提问来源于stack exchange,提问作者user9559682

