### Install Bottleneck using npm Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Installs the Bottleneck library using npm. This is the standard method for adding the package to your Node.js project. ```bash npm install --save bottleneck ``` -------------------------------- ### Job Priorities and Weights (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Explains how to schedule jobs with different priorities and weights to control execution order and resource consumption. This example sets up a limiter allowing 10 concurrent jobs and demonstrates submitting high-priority, heavy-weight, and expiring jobs. ```javascript const limiter = new Bottleneck({ maxConcurrent: 10, // Required for priorities to work minTime: 100 }); async function processJobs() { // High priority job (0 = highest, 9 = lowest, default = 5) limiter.schedule( { priority: 0, id: 'critical-job' }, () => fetch('https://api.example.com/critical') ); // Normal priority job with weight (uses 3 concurrent slots) limiter.schedule( { priority: 5, weight: 3, id: 'heavy-job' }, () => processLargeDataset() ); // Low priority job with expiration timeout limiter.schedule( { priority: 8, expiration: 5000, id: 'low-priority' }, () => fetch('https://api.example.com/background') ).catch(error => { if (error instanceof Bottleneck.BottleneckError) { console.error('Job expired or dropped:', error.message); } }); } function processLargeDataset() { return new Promise(resolve => { // Heavy computation setTimeout(() => resolve('Dataset processed'), 2000); }); } processJobs(); ``` -------------------------------- ### Token Bucket Pattern with Increase Interval (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Implements Shopify-style rate limiting using a token bucket pattern. It starts with 40 credits, adds 2 credits per second, and caps at 40 credits. Configuration includes safety limits for concurrency and job timing. The example shows how to perform API calls and manually increment the reservoir. ```javascript const limiter = new Bottleneck({ reservoir: 40, reservoirIncreaseAmount: 2, reservoirIncreaseInterval: 1000, reservoirIncreaseMaximum: 40, maxConcurrent: 5, minTime: 250 }); async function shopifyAPICall(endpoint, data) { try { const result = await limiter.schedule(() => fetch(`https://shop.example.com/api/${endpoint}`, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }).then(r => r.json()) ); return result; } catch (error) { console.error('API call failed:', error); throw error; } } limiter.incrementReservoir(10).then(newValue => { console.log('Reservoir increased to:', newValue); }); shopifyAPICall('products', { name: 'Widget' }); ``` -------------------------------- ### Import Bottleneck in ES Modules or CommonJS Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates how to import the Bottleneck library. The first example uses ES module syntax, while the second uses CommonJS, suitable for older Node.js versions or specific build configurations. It also notes the availability of an ES5 bundle for broader compatibility. ```javascript import Bottleneck from "bottleneck"; ``` ```javascript // Note: To support older browsers and Node <6.0, you must import the ES5 bundle instead. var Bottleneck = require("bottleneck/es5"); ``` -------------------------------- ### Enabling Clustering with Redis for Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Clustering allows multiple limiters to share state via Redis, ensuring distributed rate limiting. Install `redis` or `ioredis` and configure the limiter with clustering options. ```bash # NodeRedis (https://github.com/NodeRedis/node_redis) npm install --save redis # or ioredis (https://github.com/luin/ioredis) npm install --save ioredis ``` ```javascript const limiter = new Bottleneck({ /* Some basic options */ maxConcurrent: 5, minTime: 500, id: "my-super-app" // All limiters with the same id will be clustered together /* Clustering options */ datastore: "redis", // or "ioredis" clearDatastore: false, clientOptions: { host: "127.0.0.1", port: 6379 // Redis client options // Using NodeRedis? See https://github.com/NodeRedis/node_redis#options-object-properties // Using ioredis? See https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options } }); ``` ```javascript import Redis from "ioredis" const limiter = new Bottleneck({ id: "my-super-app", datastore: "ioredis", clientOptions: { host: '12.34.56.78', port: 6379 }, Redis }); ``` -------------------------------- ### Dynamic Per-Key Rate Limiting with Bottleneck Groups Source: https://context7.com/sgrondin/bottleneck/llms.txt Shows how to implement dynamic per-key rate limiting using Bottleneck.Groups, ideal for managing limits per user, IP, or API key. It includes setting up event listeners for limiter creation and errors, and provides an Express.js middleware example. ```javascript const Bottleneck = require('bottleneck'); const ipLimiters = new Bottleneck.Group({ maxConcurrent: 1, minTime: 1000, timeout: 300000 }); ipLimiters.on('created', (limiter, key) => { console.log(`New limiter created for IP: ${key}`); limiter.on('error', (error) => { console.error(`Error for IP ${key}:`, error); }); limiter.on('depleted', () => { console.warn(`Rate limit depleted for IP: ${key}`); }); }); function rateLimitMiddleware(req, res, next) { const clientIP = req.ip || req.connection.remoteAddress; const limiter = ipLimiters.key(clientIP); limiter.schedule(() => { next(); return Promise.resolve(); }).catch(error => { res.status(429).json({ error: 'Rate limit exceeded' }); }); } console.log('Active IPs:', ipLimiters.keys()); console.log('All limiters:', ipLimiters.limiters()); ipLimiters.deleteKey('192.168.1.100'); ipLimiters.updateSettings({ minTime: 2000 }); ``` -------------------------------- ### Wrap Function for Reusable Rate-Limited Functions (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Shows how to create a reusable, rate-limited version of an existing function using the `wrap` method. This example limits function calls to 1 concurrent execution with a 1000ms delay between calls. ```javascript const limiter = new Bottleneck({ maxConcurrent: 1, minTime: 1000 }); // Original function function fetchUserData(userId) { return fetch(`https://api.example.com/users/${userId}`) .then(r => r.json()); } // Create rate-limited wrapper const limitedFetchUser = limiter.wrap(fetchUserData); // Use the wrapped function - automatically rate limited async function example() { const user1 = await limitedFetchUser(123); const user2 = await limitedFetchUser(456); console.log('Users:', user1, user2); } example(); ``` -------------------------------- ### Implement Job Retries on Failure (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md This example demonstrates how to use the 'failed' event to retry a job. By returning a number of milliseconds from the 'failed' handler, the job will be retried after that delay. Returning 0 retries immediately. Retried jobs remain in the EXECUTING state. ```javascript const Bottleneck = require('bottleneck'); const limiter = new Bottleneck(); // Listen to the "failed" event limiter.on("failed", async (error, jobInfo) => { const id = jobInfo.options.id; console.warn(`Job ${id} failed: ${error}`); if (jobInfo.retryCount === 0) { // Here we only retry once console.log(`Retrying job ${id} in 25ms!`); return 25; } }); // Listen to the "retry" event limiter.on("retry", (error, jobInfo) => console.log(`Now retrying ${jobInfo.options.id}`)); const main = async function () { let executions = 0; // Schedule one job const result = await limiter.schedule({ id: 'ABC123' }, async () => { executions++; if (executions === 1) { throw new Error("Boom!"); } else { return "Success!"; } }); console.log(`Result: ${result}`); } main(); ``` -------------------------------- ### Get Running Jobs Total Weight in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Returns a promise that resolves to the total weight of all RUNNING and EXECUTING jobs across the entire Bottleneck cluster. ```javascript limiter.running() .then((count) => console.log(count)); ``` -------------------------------- ### Callback-Based API with Submit (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Demonstrates using Bottleneck with traditional callback-based asynchronous functions via the `submit` method. This example limits operations to 2 concurrent jobs with a 500ms minimum time between them. ```javascript const limiter = new Bottleneck({ maxConcurrent: 2, minTime: 500 }); function legacyAsyncOperation(arg1, arg2, callback) { // Simulated async operation setTimeout(() => { callback(null, `Result: ${arg1} + ${arg2}`); }, 100); } // Submit callback-based jobs limiter.submit(legacyAsyncOperation, 'foo', 'bar', (err, result) => { if (err) { console.error('Error:', err); } else { console.log('Success:', result); } }); // Submit with null callback if no callback needed // Note: The function must still call its callback internally limiter.submit(legacyAsyncOperation, 'baz', 'qux', null); ``` -------------------------------- ### Get All Job IDs by Status in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Returns an array of job ids filtered by the specified status. If no status is provided, returns all known job ids in the limiter. ```javascript console.log(limiter.jobs("RUNNING")); // Example: ['id1', 'id2'] ``` -------------------------------- ### Redis Clustering with IORedis and Shared Connection Source: https://context7.com/sgrondin/bottleneck/llms.txt Utilize ioredis with Redis Cluster for rate limiting in Node.js using Bottleneck. This example demonstrates creating a shared ioredis connection that multiple Bottleneck limiters can use, reducing resource overhead and simplifying connection management. ```javascript const Bottleneck = require('bottleneck'); const Redis = require('ioredis'); // Create shared connection for multiple limiters const connection = new Bottleneck.IORedisConnection({ clientOptions: { host: 'redis-cluster.example.com', port: 6379, password: 'secret', db: 0 } // For Redis Cluster: // clusterNodes: [ // { host: '127.0.0.1', port: 6379 }, // { host: '127.0.0.1', port: 6380 } // ] }); connection.on('error', (error) => { console.error('Redis connection error:', error); }); // Create multiple limiters sharing the same connection const apiLimiter = new Bottleneck({ id: 'api-limiter', maxConcurrent: 10, minTime: 100, connection: connection }); const webhookLimiter = new Bottleneck({ id: 'webhook-limiter', maxConcurrent: 5, minTime: 200, connection: connection }); // Access Redis clients directly if needed const clients = connection.clients(); console.log('Redis clients:', clients); // Close connection (closes for all limiters using it) async function shutdown() { await connection.disconnect(false); console.log('Redis connection closed'); } ``` -------------------------------- ### Get Current Reservoir Value (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The currentReservoir() method returns a promise that resolves with the current value of the limiter's reservoir. This is useful for monitoring the available capacity. ```javascript limiter.currentReservoir() .then((reservoir) => console.log(reservoir)); ``` -------------------------------- ### Increase Interval for Shopify API Rate Limits using Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Sets up a Bottleneck limiter to comply with Shopify API rate limits, starting with 40 requests and adding 2 more every second up to a maximum of 40. This configuration uses reservoirIncreaseAmount, reservoirIncreaseInterval, and reservoirIncreaseMaximum to dynamically adjust the available requests. ```javascript const limiter = new Bottleneck({ reservoir: 40, // initial value reservoirIncreaseAmount: 2, reservoirIncreaseInterval: 1000, // must be divisible by 250 reservoirIncreaseMaximum: 40, // also use maxConcurrent and/or minTime for safety maxConcurrent: 5, minTime: 250 // pick a value that makes sense for your use case }); ``` -------------------------------- ### Load Shedding with Strategies (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Demonstrates load shedding techniques to manage queue overflow using different strategies like LEAK, OVERFLOW_PRIORITY, OVERFLOW, and BLOCK. The example uses the LEAK strategy to drop the oldest low-priority jobs when the queue exceeds the highWater mark. It includes an event listener for dropped jobs. ```javascript const limiter = new Bottleneck({ maxConcurrent: 5, minTime: 200, highWater: 20, strategy: Bottleneck.strategy.LEAK }); limiter.on('dropped', (info) => { console.error('Job dropped:', { id: info.options.id, priority: info.options.priority, reason: 'Queue exceeded highWater mark' }); }); async function floodProtection() { for (let i = 0; i < 50; i++) { limiter.schedule( { priority: i % 10, id: `job-${i}` }, () => fetch(`https://api.example.com/data/${i}`) ).catch(error => { if (error instanceof Bottleneck.BottleneckError) { console.log(`Job ${i} was dropped:`, error.message); } }); } } floodProtection(); ``` -------------------------------- ### Bottleneck Constructor Initialization Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Initialize a Bottleneck limiter instance with an options object. Common options include `maxConcurrent`, `minTime`, and `highWater` to control rate limiting behavior. ```javascript const limiter = new Bottleneck({/* options */}); ``` -------------------------------- ### Submit, Schedule, and Wrap Jobs with Options in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates three methods to submit jobs to a Bottleneck limiter with advanced options. submit() executes an async call, schedule() defers function execution, and wrap() creates a reusable wrapped function. Options are passed as the first parameter before job arguments. ```javascript // Submit limiter.submit({/* options */}, someAsyncCall, arg1, arg2, callback); // Schedule limiter.schedule({/* options */}, fn, arg1, arg2); // Wrap const wrapped = limiter.wrap(fn); wrapped.withOptions({/* options */}, arg1, arg2); ``` -------------------------------- ### Get Queued Job Count in Cluster in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Asynchronously returns the total number of QUEUED jobs across the entire Bottleneck cluster, not just the local limiter instance. ```javascript const count = await limiter.clusterQueued(); console.log(count); ``` -------------------------------- ### Wait for Limiter Redis Connection with ready() Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates using the ready() method to wait for a Bottleneck limiter to successfully connect to Redis. As of v2.9.0, commands are queued until connection succeeds, but error listeners should be set to handle connection failures. ```javascript const limiter = new Bottleneck({/* options */}); limiter.on("error", (err) => { // handle network errors }); limiter.ready() .then(() => { // The limiter is ready }); ``` -------------------------------- ### Get Job Status by ID in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Returns the current lifecycle status of a specific job identified by its job id. Returns null if the job id does not exist in the limiter. ```javascript console.log(limiter.jobStatus("some-job-id")); // Example: QUEUED ``` -------------------------------- ### Access Redis Clients with clients() Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates retrieving direct access to the underlying Redis client and subscriber client used by a Bottleneck limiter for advanced use cases. ```javascript console.log(limiter.clients()); // { client: , subscriber: } ``` -------------------------------- ### Get Total Weight of DONE Jobs (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The done() method returns a promise that resolves with the total weight of 'DONE' jobs in the cluster. This method does not require the trackDoneStatus option to be enabled. ```javascript limiter.done() .then((count) => console.log(count)); ``` -------------------------------- ### Broadcast Message to Cluster with publish() Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Shows how to use the publish() method to broadcast string messages to all limiters in a cluster. For sending objects, they must be stringified first using JSON.stringify(). ```javascript const limiter = new Bottleneck({/* options */}); limiter.on("message", (msg) => { console.log(msg); // prints "this is a string" }); limiter.publish("this is a string"); ``` ```javascript limiter.on("message", (msg) => { console.log(JSON.parse(msg).hello) // prints "world" }); limiter.publish(JSON.stringify({ hello: "world" })); ``` -------------------------------- ### Schedule Async/Await Function with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Illustrates scheduling an asynchronous function using `async/await` with Bottleneck's `schedule` method. This provides a clean syntax for handling asynchronous operations under rate limiting. ```javascript const result = await limiter.schedule(() => myFunction(arg1, arg2)); ``` -------------------------------- ### Wrap Async/Await Function with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Shows how to wrap an `async/await` compatible function using Bottleneck's `wrap` method. The resulting wrapped function can be `await`ed directly, simplifying the integration of rate limiting with modern asynchronous JavaScript. ```javascript const wrapped = limiter.wrap(myFunction); const result = await wrapped(arg1, arg2); ``` -------------------------------- ### Schedule Job with Promise/Async-Await using Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Shows how to add a job to the Bottleneck queue using the `schedule` method, suitable for Promises and async/await. The `schedule` method takes a function and its arguments, returning a Promise that resolves with the job's result once rate limits are met. ```javascript const fn = function(arg1, arg2) { return httpGet(arg1, arg2); // Here httpGet() returns a promise }; limiter.schedule(fn, arg1, arg2) .then((result) => { /* ... */ }); ``` ```javascript // suppose that `client.get(url)` returns a promise const url = "https://wikipedia.org"; limiter.schedule(() => client.get(url)) .then(response => console.log(response.body)); ``` -------------------------------- ### Get Queued Job Count in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Returns the number of jobs currently in the QUEUED state, optionally filtered by priority level. Omitting the priority argument returns the total count of all queued jobs in the limiter. ```javascript const count = limiter.queued(priority); console.log(count); ``` -------------------------------- ### Submit Callback-based Function with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates submitting a function that uses callbacks to Bottleneck's `submit` method. This allows for rate-limited execution of older asynchronous patterns. ```javascript limiter.submit(someAsyncCall, arg1, arg2, callback); ``` -------------------------------- ### Get Job Counts by Status in Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Retrieves an object containing the current count of jobs in each lifecycle state within the limiter. States include RECEIVED, QUEUED, RUNNING, EXECUTING, and DONE (only tracked if trackDoneStatus is enabled during limiter creation). ```javascript const counts = limiter.counts(); console.log(counts); /* { RECEIVED: 0, QUEUED: 0, RUNNING: 0, EXECUTING: 0, DONE: 0 } */ ``` -------------------------------- ### Dynamically Update Bottleneck Settings and Manage Reservoir Source: https://context7.com/sgrondin/bottleneck/llms.txt Demonstrates how to dynamically update Bottleneck limiter settings such as maxConcurrent, minTime, and reservoir properties. It also shows how to check and manually adjust the current reservoir, and determine if a job would run immediately or be queued, with or without a specified weight. ```javascript const limiter = new Bottleneck({ maxConcurrent: 5, minTime: 1000, reservoir: 50 }); // Update settings dynamicallylimiter.updateSettings({ maxConcurrent: 10, minTime: 500, reservoir: 100, reservoirRefreshAmount: 100, reservoirRefreshInterval: 60000 }); console.log('Settings updated'); // Check current reservoirlimiter.currentReservoir().then(value => { console.log('Current reservoir:', value); }); // Manually increment reservoirlimiter.incrementReservoir(25).then(newValue => { console.log('Reservoir increased to:', newValue); }); // Check if job would run immediatelylimiter.check().then(wouldRun => { if (wouldRun) { console.log('Job would execute immediately'); } else { console.log('Job would be queued'); } }); // Check with specific weightlimiter.check(5).then(wouldRun => { console.log(`Job with weight 5 would run: ${wouldRun}`); }); // Get number of queued jobs const totalQueued = limiter.queued(); const highPriorityQueued = limiter.queued(0); console.log(`Queued: ${totalQueued} (${highPriorityQueued} high priority)`); ``` -------------------------------- ### Wrap Function for Rate Limiting using Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Explains how to use the `wrap` method to create a rate-limited version of an existing function that returns a Promise. The returned function behaves identically to the original but adheres to the configured rate limits, including potential rejections due to expiration times. ```javascript const wrapped = limiter.wrap(fn); wrapped() .then(function (result) { /* ... */ }) .catch(function (error) { // Bottleneck might need to fail the job even if the original function can never fail. // For example, your job is taking longer than the `expiration` time you've set. }); ``` -------------------------------- ### Binding Object Methods for Schedule/Wrap Source: https://github.com/sgrondin/bottleneck/blob/master/README.md When scheduling an object's method, you often need to explicitly bind the object's context using `.bind(object)` or by wrapping it in an arrow function to maintain the correct `this` reference. ```javascript // do this: limiter.schedule(object.doSomething.bind(object)); // or, wrap it in an arrow function instead: limiter.schedule(() => object.doSomething()); ``` -------------------------------- ### Listen for Limiter Creation in Bottleneck Group Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'created' event on a Bottleneck Group allows you to execute code whenever a new limiter is instantiated for a specific key. This is the recommended way to set up new limiters, for instance, to attach event listeners like 'error'. ```javascript group.on("created", (limiter, key) => { console.log("A new limiter was created for key: " + key) // Prepare the limiter, for example we'll want to listen to its "error" events! limiter.on("error", (err) => { // Handle errors here }) }); ``` -------------------------------- ### Automatic Job Retries with Failed Event (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Configures a Bottleneck limiter to automatically retry failed jobs with exponential backoff. It sets retry limits and defines delay intervals for retries using the 'failed' event listener. The example includes a simulated unreliable operation and schedules a job with an ID for debugging. ```javascript const limiter = new Bottleneck({ maxConcurrent: 3, minTime: 500 }); limiter.on('failed', async (error, jobInfo) => { const { id, retryCount } = { id: jobInfo.options.id, retryCount: jobInfo.retryCount }; console.warn(`Job ${id} failed (attempt ${retryCount}):`, error.message); if (retryCount < 3) { const delay = Math.pow(2, retryCount) * 1000; console.log(`Retrying job ${id} in ${delay}ms`); return delay; } else { console.error(`Job ${id} permanently failed after 3 retries`); } }); limiter.on('retry', (error, jobInfo) => { console.log(`Now retrying job: ${jobInfo.options.id}`); }); async function unreliableOperation(data) { if (Math.random() < 0.6) { throw new Error('Network timeout'); } return `Success: ${data}`; } limiter.schedule( { id: 'job-001' }, () => unreliableOperation('test-data') ).then(result => { console.log('Final result:', result); }).catch(error => { console.error('Job failed permanently:', error); }); ``` -------------------------------- ### Catch and identify BottleneckError exceptions Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates how to distinguish Bottleneck-specific errors from application errors using instanceof check with Bottleneck.BottleneckError. This pattern helps differentiate error sources when scheduling jobs fails. ```javascript limiter.schedule(fn) .then((result) => { /* ... */ } ) .catch((error) => { if (error instanceof Bottleneck.BottleneckError) { /* ... */ } }); ``` -------------------------------- ### Configure Bottleneck for Rate Limiting (3 requests/sec) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Initializes a Bottleneck limiter with a `minTime` setting to enforce a rate of approximately 3 requests per second. The `minTime` value is calculated as 1000ms / 3 requests = 333ms. ```javascript const limiter = new Bottleneck({ minTime: 333 }); ``` -------------------------------- ### Wrap Promise-based Function with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates using Bottleneck's `wrap` method to create a rate-limited version of a function. The wrapped function can then be called directly, with Bottleneck handling the scheduling and rate limiting internally. ```javascript const wrapped = limiter.wrap(myFunction); wrapped(arg1, arg2) .then((result) => { /* handle result */ }); ``` -------------------------------- ### Basic Rate Limiting with Promises (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Demonstrates how to set up basic rate limiting using promises to control API calls. It configures a minimum time between requests (333ms for 3 requests/sec) and schedules a fetch operation. ```javascript const Bottleneck = require('bottleneck'); const limiter = new Bottleneck({ minTime: 333 // 1000ms / 3 = 333ms between requests }); // Promise-based API calllimiter.schedule(() => fetch('https://api.example.com/data')) .then(response => response.json()) .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error)); ``` -------------------------------- ### Update Limiter Settings (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The updateSettings() method allows dynamic modification of limiter configuration. The accepted options are the same as those used in the limiter constructor. Changes do not affect jobs that are already scheduled. ```javascript limiter.updateSettings(options); ``` -------------------------------- ### ES5 Compatibility Build for Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Bottleneck requires Node 6+ for its main build. If you need compatibility with older environments, an ES5-compatible build is available via `require("bottleneck/es5")`. ```javascript var Bottleneck = require("bottleneck/es5"); ``` -------------------------------- ### Schedule Promise-based Function with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Shows how to use the `schedule` method of Bottleneck to execute a promise-returning function within the rate limiter's constraints. It replaces direct promise calls with a scheduled execution. ```javascript limiter.schedule(() => myFunction(arg1, arg2)) .then((result) => { /* handle result */ }); ``` -------------------------------- ### Create and Use Bottleneck Group for Dynamic Limiter Management Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The Bottleneck.Group class manages multiple limiters dynamically. It creates individual limiters based on keys (e.g., IP addresses) and applies shared options to all created limiters. This is ideal for scenarios requiring independent rate limiting for a large number of entities. ```javascript const group = new Bottleneck.Group(options); // In this example, the key is an IP group.key("77.66.54.32").schedule(() => { /* process the request */ }); ``` -------------------------------- ### Access and reuse Connection object from existing Group Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Demonstrates how to extract the connection object from an existing limiter and reuse it when creating a new Group. This pattern allows multiple Bottleneck instances to share the same underlying Redis clients. ```javascript const group = new Bottleneck.Group({ connection: limiter.connection }); ``` -------------------------------- ### Implement Debug Monitoring and Comprehensive Error Handling in Bottleneck Source: https://context7.com/sgrondin/bottleneck/llms.txt Details how to set up robust error handling and debugging for Bottleneck limiters. This includes listening for 'error', 'debug', and 'failed' events, implementing retry logic for failed jobs, and monitoring queue statistics to prevent overload. ```javascript const limiter = new Bottleneck({ maxConcurrent: 3, minTime: 500, id: 'debug-limiter' }); // Critical: Always handle errorslimiter.on('error', (error) => { console.error('Limiter error:', error); // Log to error tracking service // Sentry.captureException(error); }); // Debug detailed internal operationslimiter.on('debug', (message, data) => { console.log('[DEBUG]', message, data); }); // Failed job handler with debugging infolimiter.on('failed', async (error, jobInfo) => { console.error('Job failed:', { id: jobInfo.options.id, error: error.message, retryCount: jobInfo.retryCount, args: jobInfo.args, options: jobInfo.options }); // Retry logic with detailed logging if (jobInfo.retryCount < 2) { console.log(`Retrying ${jobInfo.options.id} (attempt ${jobInfo.retryCount + 1})`); return 1000 * (jobInfo.retryCount + 1); } }); // Schedule job with comprehensive error handlinglimiter.schedule( { id: 'api-call-001', priority: 5, weight: 1, expiration: 5000 // Fail if takes >5 seconds }, () => fetch('https://api.example.com/data').then(r => r.json()) ).then(result => { console.log('Success:', result); }).catch(error => { if (error instanceof Bottleneck.BottleneckError) { console.error('Bottleneck error:', error.message); // Job was dropped, expired, or limiter stopped } else { console.error('Application error:', error); // Error from your code } }); // Monitor queue length and prevent overload setInterval(() => { const counts = limiter.counts(); console.log('Queue stats:', counts); if (counts.QUEUED > 100) { console.warn('Queue is getting long, consider scaling up'); } }, 5000); ``` -------------------------------- ### Configure Bottleneck for Rate Limiting and Concurrency Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Initializes a Bottleneck limiter to ensure no more than one request runs concurrently (`maxConcurrent: 1`) and maintains a minimum time between requests (`minTime: 333`) to achieve approximately 3 requests per second. This configuration prevents requests from overlapping and ensures a controlled rate. ```javascript const limiter = new Bottleneck({ maxConcurrent: 1, minTime: 333 }); ``` -------------------------------- ### Rate Limiting with Async/Await (JavaScript) Source: https://context7.com/sgrondin/bottleneck/llms.txt Illustrates controlling concurrent requests using async/await syntax. It sets a maximum number of simultaneous jobs (5) and a minimum time between job launches (200ms), processing items sequentially with rate limiting. ```javascript const limiter = new Bottleneck({ maxConcurrent: 5, // Max 5 jobs running at once minTime: 200 // Wait 200ms between launching jobs }); async function processItems(items) { try { for (const item of items) { const result = await limiter.schedule(() => processItem(item)); console.log(`Processed: ${result}`); } } catch (error) { console.error('Processing failed:', error); } } async function processItem(item) { // Your async operation here const response = await fetch(`https://api.example.com/items/${item.id}`); return response.json(); } processItems([{id: 1}, {id: 2}, {id: 3}]); ``` -------------------------------- ### Retrieve All Keys from a Bottleneck Group Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The keys() method returns an array containing all the keys currently managed by the Bottleneck Group. This provides a snapshot of all active underlying limiters. ```javascript const allKeys = group.keys(); console.log(allKeys); ``` -------------------------------- ### Retrieve All Limiters from a Bottleneck Group Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The limiters() method returns an array of objects, where each object contains a 'key' and its corresponding 'limiter' instance. This allows direct access to the individual limiters managed by the group. ```javascript const allLimiters = group.limiters(); console.log(allLimiters); // [ { key: "some key", limiter: }, { key: "some other key", limiter: } ] ``` -------------------------------- ### Handle Debug Information (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'debug' event provides real-time information about the limiter's internal operations. It's useful for debugging application logic and understanding limiter behavior. ```javascript limiter.on("debug", function (message, data) { // Useful to figure out what the limiter is doing in real time // and to help debug your application }); ``` -------------------------------- ### Check if Job Would Run Immediately (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The check() method returns a promise that resolves to a boolean indicating whether a new job would be executed immediately if submitted. This is useful for pre-flight checks before job submission. ```javascript limiter.check() .then((wouldRunNow) => console.log(wouldRunNow)); ``` -------------------------------- ### Handle Limiter Empty State (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'empty' event is triggered when the limiter's queue becomes empty, indicated by limiter.empty() returning true. This can be used for cleanup or to signal completion. ```javascript limiter.on("empty", function () { // This will be called when `limiter.empty()` becomes true. }); ``` -------------------------------- ### Handle Limiter Idle State (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'idle' event fires when the limiter is empty (limiter.empty() is true) and no jobs are currently running (limiter.running() is 0). This signifies a period of inactivity. ```javascript limiter.on("idle", function () { // This will be called when `limiter.empty()` is `true` and `limiter.running()` is `0`. }); ``` -------------------------------- ### Handle Job Lifecycle Transitions (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Lifecycle events like 'queued', 'scheduled', 'executing', and 'done' are triggered when a job transitions between stages. Note that these are not triggered for jobs on other cluster limiters for performance reasons. ```javascript limiter.on("queued", function (info) { // This event is triggered when a job transitions from one Lifecycle stage to another }); ``` -------------------------------- ### Reuse existing NodeRedis or ioredis client with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Configures Bottleneck to reuse an existing Redis client instance instead of creating a new one. The Connection object will still create a second client for pubsub operations. ClientOptions and clusterNodes are ignored when a raw client is provided. ```javascript import Redis from "redis"; const client = new Redis.createClient({/* options */}); const connection = new Bottleneck.RedisConnection({ // `clientOptions` and `clusterNodes` will be ignored since we're passing a raw client client: client }); const limiter = new Bottleneck({ connection: connection }); const group = new Bottleneck.Group({ connection: connection }); ``` -------------------------------- ### Catching Bottleneck Error Events Source: https://github.com/sgrondin/bottleneck/blob/master/README.md It is crucial to listen for and handle the 'error' events emitted by your Bottleneck limiter instances to gracefully manage job failures. -------------------------------- ### Handle Dropped Requests (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'dropped' event is emitted when a request is dropped due to a triggered strategy. It passes the dropped request object to the listener for inspection. ```javascript limiter.on("dropped", function (dropped) { // This will be called when a strategy was triggered. // The dropped request is passed to this event listener. }); ``` -------------------------------- ### Handle Job Failures (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'failed' event is emitted whenever a job fails. It provides the error object and job information, allowing for custom failure handling logic. ```javascript limiter.on("failed", function (error, jobInfo) { // This will be called every time a job fails. }); ``` -------------------------------- ### Create shared RedisConnection for Group and Limiter Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Creates a single Bottleneck.RedisConnection object that is shared between a limiter and a group, reducing the number of Redis clients needed. The connection accepts NodeRedis or ioredis options, including clusterNodes for cluster configurations. ```javascript const connection = new Bottleneck.RedisConnection({ clientOptions: {/* NodeRedis/ioredis options */} // ioredis also accepts `clusterNodes` here }); const limiter = new Bottleneck({ connection: connection }); const group = new Bottleneck.Group({ connection: connection }); ``` -------------------------------- ### Gracefully Shut Down Bottleneck Limiters and Clean Up Resources Source: https://context7.com/sgrondin/bottleneck/llms.txt Explains how to safely stop Bottleneck limiters, differentiating between graceful shutdown (waiting for executing jobs) and force shutdown (dropping waiting jobs). It also covers disconnecting from Redis and handling process termination signals like SIGTERM and SIGINT. ```javascript const limiter = new Bottleneck({ maxConcurrent: 5, minTime: 500, reservoir: 100, reservoirRefreshInterval: 60000, reservoirRefreshAmount: 100 }); // Schedule some jobs for (let i = 0; i < 20; i++) { limiter.schedule( { id: `job-${i}` }, () => new Promise(resolve => setTimeout(resolve, 2000)) ); } // Graceful shutdown: wait for executing jobs to complete async function gracefulShutdown() { console.log('Starting graceful shutdown...'); await limiter.stop({ dropWaitingJobs: false, // Let queued jobs complete dropErrorMessage: 'Shutdown in progress', enqueueErrorMessage: 'Server is shutting down' }); console.log('All jobs completed'); limiter.disconnect(); } // Force shutdown: drop all waiting jobs immediately async function forceShutdown() { console.log('Force stopping limiter...'); await limiter.stop({ dropWaitingJobs: true, // Drop all waiting jobs dropErrorMessage: 'Server shutdown - job cancelled' }); console.log('Limiter stopped'); limiter.disconnect(); } // Disconnect from Redis and clear reservoir intervallimiter.disconnect(true).then(() => { console.log('Disconnected and flushed'); }); // Handle process termination process.on('SIGTERM', gracefulShutdown); process.on('SIGINT', forceShutdown); ``` -------------------------------- ### Chain Bottleneck Limiters for Combined Rate Limiting Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The chain() method allows you to link multiple Bottleneck limiters together. This is useful when tasks need to adhere to the rate limits of both their specific limiter and a global limiter. To unchain, call chain(null). ```javascript const limiterA = new Bottleneck( /* some settings */ ); const limiterB = new Bottleneck( /* some different settings */ ); const limiterG = new Bottleneck( /* some global settings */ ); limiterA.chain(limiterG); limiterB.chain(limiterG); // Requests added to limiterA must follow the A and G rate limits. // Requests added to limiterB must follow the B and G rate limits. ``` -------------------------------- ### Safely Shutdown Bottleneck Limiter using stop() Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The stop() method gracefully shuts down a Bottleneck limiter. It stops new jobs from being added and ensures all currently executing jobs are completed. It returns a promise that resolves when the shutdown is finished, with an option to drop waiting jobs. ```javascript limiter.stop(options) .then(() => { console.log("Shutdown completed!") }); ``` -------------------------------- ### Handle Reservoir Depletion (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'depleted' event is triggered every time the limiter's reservoir count drops to zero. It provides a boolean indicating if the limiter is currently empty. ```javascript limiter.on("depleted", function (empty) { // This will be called every time the reservoir drops to 0. // The `empty` (boolean) argument indicates whether `limiter.empty()` is currently true. }); ``` -------------------------------- ### Handle Job Retries (JavaScript) Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The 'retry' event is called each time a job is retried. It logs a message and provides job information, useful for tracking retry attempts. ```javascript limiter.on("retry", function (message, jobInfo) { // This will be called every time a job is retried. }); ``` -------------------------------- ### Batching API Operations with Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md The Batcher class allows grouping multiple operations into a single API call. It flushes batches based on maximum time or size. Adding items returns a promise that resolves when the item is flushed. ```javascript const batcher = new Bottleneck.Batcher({ maxTime: 1000, maxSize: 10 }); batcher.on("batch", (batch) => { console.log(batch); // ["some-data", "some-other-data"] // Handle batch here }); batcher.add("some-data"); batcher.add("some-other-data"); ``` -------------------------------- ### Throttle to 100 Requests Every 60 Seconds using Bottleneck Source: https://github.com/sgrondin/bottleneck/blob/master/README.md Configures a Bottleneck limiter to allow 100 requests initially and replenish 100 requests every 60 seconds. It also sets safety limits for maximum concurrent jobs and minimum time between jobs. The reservoir is a counter that decreases with each job and is reset at specified intervals. ```javascript const limiter = new Bottleneck({ reservoir: 100, // initial value reservoirRefreshAmount: 100, reservoirRefreshInterval: 60 * 1000, // must be divisible by 250 // also use maxConcurrent and/or minTime for safety maxConcurrent: 1, minTime: 333 // pick a value that makes sense for your use case }); ```