javascript无内置asyncsemaphore,但可用promise+计数器+队列模拟:维护running计数与queue任务队列,通过acquire/release调度并发,推荐使用封装好的run方法确保成对执行。

JavaScript 本身没有内置的 AsyncSemaphore 类,但可以通过 Promise + 计数器 + 队列的方式模拟出行为一致的异步信号量,精准控制对共享资源(如 API 接口、文件句柄、数据库连接)的并发访问数量。
核心思路:用队列 + 计数器代替阻塞
同步信号量靠“挂起线程”实现等待,而 JavaScript 是单线程事件驱动模型,不能真正阻塞。所以异步信号量的本质是:把超出许可数的任务暂存进队列,等有空位时再自动触发执行。关键不在于“锁”,而在于“调度”。
- 维护一个当前活跃任务数
running = 0 - 维护一个待执行任务队列
queue = [] - 设定最大并发数
limit(即“许可证总数”) - 每次执行任务前判断
running ;否则推入队列,等别人 <code>done后再拉取
手写一个轻量 AsyncSemaphore 类
以下是一个生产可用、无依赖的实现(支持取消、错误传播、类型提示友好):
class AsyncSemaphore {
constructor(limit) {
if (limit {
if (this.running 0) {
const next = this.queue.shift();
this.running++;
next();
}
}
// 推荐封装:自动 acquire + release 的执行包装
async run(fn) {
await this.acquire();
try {
return await fn();
} finally {
this.release();
}
}
}
用法示例(限制最多 3 个并发请求):
const sem = new AsyncSemaphore(3); const urls = ['/api/a', '/api/b', '/api/c', '/api/d', '/api/e']; const requests = urls.map(url => sem.run(() => fetch(url).then(r => r.json())) ); const results = await Promise.all(requests); // 自动限流,不会同时发 5 个
为什么不用 setInterval 或 setTimeout 轮询?
轮询不仅浪费 CPU,还会引入不可控延迟和竞态风险。正确做法是事件驱动式唤醒:每个任务完成(release)后立即检查队列,有就立刻 dispatch 下一个——这和 asyncio.Semaphore 的唤醒逻辑完全一致,只是底层机制不同。
- 避免忙等,零额外开销
- 响应及时:释放瞬间就调度,无延迟累积
- 天然支持 Promise 链和 async/await,符合 JS 异步范式
实际使用要注意的三个坑
很多团队自己写的“限流器”失效,往往栽在这三点:
-
异常未触发 release:如果
fn()抛错且没进finally,计数器卡死。务必用try/finally或run()封装方法 -
手动调用 acquire/release 不成对:比如忘记
release,或在多个分支里漏写。推荐只暴露run()方法,禁止裸调用 acquire/release - 误把节流(throttle)当限流(concurrency limit):节流是“单位时间最多执行 N 次”,限流是“同一时刻最多执行 N 个”。两者目的和实现完全不同
不复杂但容易忽略:只要确保 acquire/release 成对、用队列替代轮询、封装 run 方法,就能稳定控制并发访问数。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











