在 React 控制异步并发请求数的实现

最后更新于 March 3, 2023 • 5 min read

背景

最近有一个业务需求,需要前端批量获取某个列表的数据。

利用该列表数据的 id 字段去查询相对应的详情数据 detail,输出为 Excel 报表。

由于数据量比较多,期望前端这边增加进度条展示,让用户感知此时任务的进度。

需求拆解

获取列表的所有 id,通过一次请求可以实现,比较取巧的做法是通过查询列表接口将 page_size (每页展示条数),往大了写。

通过 所有 id 去查询 detail,这个过程理论上列表有 N 条需要查询 N 次,通过循环的方式可以解。

typescript

for await 虽然可以解,但是这样每个请求都是顺序的,理论上处理 N 条数据,每条 T 秒,耗时 N * T秒。不是一个最佳解法

通过 Promise.all 解决

typescript

Promise.all 提供了并发,能够节省一些请求的时间。

但缺点在于:我们无法得知剩余的任务数量,以及我们无法控制并发数,在数量较大的情况下,会造成请求的堵塞。

想法

既要通过并发去优化请求的时间,又需要增加并发的限制数,且需要在并发执行的过程中去计算任务的数量。

一个思路是通过拆分的方式去创造一个异步任务,将一个大的 Promise.all 任务拆分成若干个小的 Promise.all 任务。

我们可以尝试增加一个 limit 变量,去作为并发的限制,增加一个 taskIndex 去作为当前任务的索引值

主要的计算公式就是

typescript

本质上是通过 taskIndex 作为依赖项,去循环触发 useEffect事件 ,每一次循环 useEffect 事件调用 runTask 方法,截取需要异步执行的任务。执行完毕后再更新 taskIndex,直到没有需要处理的参数

typescript

解决

通过实际编码实现,封装成 useAsyncPool 这个 React Hooks

具体的编码实现比较简单,中心思想还是把大的任务拆分为小的,然后 useEffect 去循环,直到所有任务结束

typescript

Github 仓库: Github 仓库

useAsyncPool 文档: 文档

npm 包地址: npm 包

优化

尽管开发 useAsyncPool 这个npm 包实现了上述的需求,但依然有一些不足需要解决:

缺少容错

直接通过 async await 获取异步数据,最好还要用 try catch 包一下,做异常的处理和数据的容错。

心智负担太大

举个例子,还是回到最初提到的那个例子,获取 list,通过 listid 查询 接口A。

假设后期需求变更,请求完接口A之后,还需要用接口A的 uid 字段再去请求 B 接口。

流程就变成了:

null

那么伪代码可能是这样的:

typescript

那B接口请求完再请求C接口呢?我们会发现心智负担出现了:

开发者需要通过 useEffect + 条件判断 去判断当前进行的任务是否完成,能否进行下一个任务。

这个心智负担是不应该由开发者去承担的,后续的版本可能会考虑一些优化方向:

1.一个组件创建多个 useAsyncPool,由单一实例的类 Pool 去进行任务调度。这样可以将代码抽取得更干净

2.useAsyncPool 增加一个配置项 dependency

,作用是通过当前任务的加载状态和数据完整程度,去判断下一个任务是否执行。

3.实现一个 useTasks,用于控制对串联任务的调度,比如接口A请求完再请求B,这样的任务调度是否可以描述


Built with Next.js • Deployed on Vercel
©2025 Wongchisum