Token bucket rate limiter — async FIFO, lazy refill, no active timer.
Module tokenBucket | Source packages/front/fw/src/io/sync/tokenBucket.js | Deps none | Worker-safe yes
Rate limiting primitive. A bucket has a maximum capacity and refills at a configurable rate. Consumers take tokens (tryTake sync or take async). The refill is computed on demand (lazy) — no active internal timer.
Distinct from rateLimit (debounce/throttle UX on functions). tokenBucket is a logical primitive: available resource quantity, burst management, backpressure.
Resolve
const tokenBucket = runtime.resolve('tokenBucket');
// Returns: { create }
API
| Method | Signature | Returns |
|---|---|---|
create |
(options) => Bucket |
Bucket instance |
create options
| Option | Type | Default | Description |
|---|---|---|---|
capacity |
integer >= 1 |
— | Max simultaneous tokens |
refillRate |
number >= 0 |
— | Tokens added per refillInterval |
refillInterval |
number |
1000 |
Refill interval (ms) |
initial |
number |
capacity |
Initial tokens |
Bucket object
| Method / Property | Signature | Description |
|---|---|---|
tryTake |
(n?: number = 1) => boolean |
Sync — takes if available, false otherwise |
take |
(n?: number = 1) => Promise<void> |
Async — waits until tokens are available |
available |
getter: number |
Tokens available computed on demand |
capacity |
getter: number |
Max capacity (immutable) |
refillRate |
getter: number |
Refill rate (immutable) |
refillInterval |
getter: number |
Interval (ms) (immutable) |
reset |
() => void |
Resets to capacity |
cancel |
() => void |
Rejects all async waiters |
Semantics:
take(n)ifn > capacity→ immediate rejection (impossible to satisfy).- FIFO queue: if multiple waiters, they are resolved in call order.
cancel()rejects all waiters withError('tokenBucket: cancelled').refillRate: 0→ fixed bucket with no refill (useful for burst limited by reset or external trigger).
Examples
HTTP rate limiting (max 10 req/s)
const tokenBucket = runtime.resolve('tokenBucket');
const bucket = tokenBucket.create({
capacity: 10,
refillRate: 10,
refillInterval: 1000,
});
async function rateLimitedFetch(url) {
await bucket.take(); // waits if rate exceeded
return fetch(url);
}
Backpressure with channel
const channel = runtime.resolve('channel');
const tokenBucket = runtime.resolve('tokenBucket');
const ch = channel.create({ capacity: 100 });
const bucket = tokenBucket.create({ capacity: 5, refillRate: 5, refillInterval: 1000 });
async function consumer() {
while (true) {
await bucket.take(); // max 5 items/s
const item = await ch.recv();
process(item);
}
}
Fixed bucket (burst control)
const bucket = tokenBucket.create({ capacity: 3, refillRate: 0, initial: 3 });
// Allow 3 fast actions, block afterwards until reset()
if (bucket.tryTake()) doAction();
// Later
bucket.reset(); // Recharges
Worker Usage
No DOM dependency — usable directly in workers.
const worker = fw.createWorker(
async function ({ libs, args }) {
const bucket = libs.tokenBucket.create({ capacity: 5, refillRate: 5, refillInterval: 1000 });
for (const url of args.urls) {
await bucket.take();
const resp = await fetch(url);
self.postMessage(await resp.json());
}
},
{ dependencies: ['tokenBucket'], args: { urls: [...] } }
);
Notes
- Lazy refill: no
setInterval— tokens computed on eachtryTake/take/available. No memory leak when idle. availablereturnsMath.floor(_tokens)— the available integer quantity.- After
cancel(), subsequenttake()calls reject immediately. - For more complex patterns (retry, backoff): combine
tokenBucketwithabort.