闭包实现的计数信号量是前端控制大文件分片上传并发数最轻量可靠的方式,它通过私有状态(可用许可数和等待队列)封装 acquire/release 逻辑,确保严格限制并发并自动排队调度。

用闭包实现计数信号量,是前端控制大文件分片上传并发数最轻量、最可靠的方式之一。它不依赖外部状态管理库,也不需要全局变量,所有并发逻辑封装在函数内部,天然避免竞态和污染。
什么是计数信号量?
它本质是一个带容量限制的“许可池”:每次上传一个分片前,先申请一个许可;上传完成(无论成功或失败)后,归还许可。池中许可数量决定了当前最多能并行几个请求。
比如设最大并发为 3,就相当于初始化一个含 3 个“空位”的队列;第 4 个分片必须等前面某个完成腾出空位后才能开始。
闭包如何封装这个信号量
核心是用闭包保存两个私有状态:当前可用许可数 和 等待队列。外部只能通过返回的 acquire / release 方法操作,无法直接修改内部计数器。
立即学习“前端免费学习笔记(深入)”;
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
示例代码结构:
const createSemaphore = (max: number) => {
let available = max;
const queue: Array<() => void> = [];
return {
acquire: () => {
return new Promise<void>(resolve => {
if (available > 0) {
available--;
resolve();
} else {
queue.push(resolve);
}
});
},
release: () => {
if (queue.length > 0) {
const next = queue.shift();
next?.();
} else {
available++;
}
}
};
};
调用 const sem = createSemaphore(3) 后,sem.acquire() 返回 Promise,只有拿到许可才 resolve;sem.release() 归还许可并唤醒下一个等待者。
怎么把它用在切片上传里
把 acquire 放在每个分片上传前,release 放在上传完成(包括 catch)之后。确保每个分片都走完“申请→执行→释放”闭环。
- 上传函数包装示例:
const uploadChunk = async (chunk: Blob, index: number) => {
await sem.acquire(); // 等待空位
try {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('index', String(index));
await fetch('/upload', { method: 'POST', body: formData });
} finally {
sem.release(); // 必须释放,即使出错
}
};
- 启动全部分片时,直接并发触发所有 uploadChunk,但实际执行受信号量节流
- 不需要手写队列调度或递归调用,Promise 自动排队
- 错误不会卡死信号量——finally 保证 release 执行
为什么比 Promise.all + 数组切片更合适
Promise.all([p1,p2,p3,p4,p5]) 会一次性发起 5 个请求,超出浏览器或服务端承受能力;而信号量方式让请求真正“流动起来”:3 个跑着,第 4 个在等,第 5 个还在队列尾。它适应网络波动,也便于扩展暂停/恢复逻辑。
如果后续要加上传暂停功能,只需让 acquire 拒绝 resolve,再暴露一个 resume 方法清空队列并重新触发即可,不破坏原有结构。

















