异步节流是专为控制异步任务并发数设计的调度策略,核心是限制同时pending的promise数量而非调用频率;它通过promise.race+并发池实现动态腾位、错误重试与顺序保证,并可结合带宽感知动态调整最大并发数。

异步节流(Async Throttle)不是传统节流(throttle)的简单移植,而是专为控制异步任务并发数设计的一种调度策略——它不压制调用频率,而是限制“同时处于 pending 状态的 Promise 数量”。在大文件上传场景中,这直接对应“同时上传的分片数”,是保护浏览器内存、服务端负载和用户带宽的关键手段。
为什么不能只靠 Promise.all 或 setTimeout?
Promise.all 会一次性发起全部请求,极易压垮浏览器连接池或触发服务端限流;setTimeout 手动延时则缺乏动态反馈,无法响应失败重试或网络波动。真正的异步节流需满足:可配置最大并发数、自动腾出空位、支持错误后重入、保留原始执行顺序语义。
用 Promise.race + 并发池实现轻量节流
这是最常用且无需额外库的方案,核心是维护一个固定长度的 Promise 队列(“池”),每次只允许最多 N 个上传任务并行运行:
- 定义 MAX_CONCURRENT = 4,创建空数组 pool 存储当前活跃请求
- 遍历每个分片,构造 uploadChunk(index) 返回 Promise
- 将该 Promise 推入 pool;若 pool 长度达上限,则 await Promise.race(pool),等任一完成即移除它,再继续推新任务
- 所有分片提交完毕后,await Promise.all(pool) 确保最后一批也完成
这种方式天然平滑:网络快时多个任务自然并行;某一分片卡住,其他任务仍能借空位推进,不会整体阻塞。
封装成可复用的 asyncThrottle 工具函数
把逻辑抽象为高阶函数,便于多处复用:
function asyncThrottle(max, fn) {
const pool = [];
return async function(...args) {
const task = fn(...args);
pool.push(task);
if (pool.length >= max) {
await Promise.race(pool);
const idx = pool.indexOf(task);
if (idx > -1) pool.splice(idx, 1);
}
return task;
};
}
// 使用示例
const uploadWithLimit = asyncThrottle(3, uploadChunk);
for (let i = 0; i
<p>注意:此处 await 是为了串行提交任务(避免瞬间堆积),实际上传仍是并发执行——节流控制的是“飞行中请求数”,不是调用节奏。</p>
<h3>结合带宽感知做动态调整(进阶)</h3>
<p>纯固定并发数不够智能。可配合 navigator.connection.effectiveType 或 fetch 的 AbortSignal + performance.now() 估算单分片平均耗时,动态升降 MAX_CONCURRENT:</p>
- 连续 3 次上传耗时 > 8s → 降为 2
- 连续 5 次成功且均
- 监听 connection.downlink 变化,低于 2 Mbps 时强制锁死为 2
这种自适应机制让上传更贴合真实网络,尤其对移动弱网用户友好,且不增加服务端改造成本。










