p-limit

repository·main·Indexed 25 days ago

https://github.com/sindresorhus/p-limit

A utility for running multiple promise-returning and async functions with limited concurrency in Node.js and browser environments. Version 7.3.1 provides a LimitFunction API to queue tasks, process iterables via limit.map(), and monitor state using activeCount and pendingCount. It includes features to dynamically adjust concurrency, clear the task queue, and a limitFunction named export for wrapping specific functions with their own concurrency management.

Tokens
4.2K
Snippets
13
Records
24
Agent score
81%

What's inside p-limit

  1. Pass shared context using limit()

    main

    The limit(fn, ...args) syntax allows you to pass shared arguments (like a database client or configuration object) to every function call managed by the limiter.

    import pLimit from 'p-limit';
    import {S3} from '@aws-sdk/client-s3';
    
    const limit = pLimit(2);
    const client = new S3({});
    
    const runWithClient = (function_, ...arguments_) => limit(function_, client, ...arguments_);
    
    const fetchFromSomeBucket = (client, fileKey) => client.getObject({
    	Bucket: 'someBucket',
    	Key: fileKey
    });
    
    const results = await Promise.all([
    	runWithClient(fetchFromSomeBucket, 'someFileKey1'),
    	runWithClient(fetchFromSomeBucket, 'someFileKey2'),
    	runWithClient(fetchFromSomeBucket, 'someFileKey3')
    ]);
  2. Implement graceful shutdown

    main

    When shutting down your application, use limit.clearQueue() to discard all tasks that haven't started yet. Note that clearQueue() causes the promises for discarded tasks to never settle. To shut down cleanly, you should track currently running promises and wait for them to complete before exiting.

    import pLimit from 'p-limit';
    
    const limit = pLimit(5);
    
    const urls = getUrls(); // Assume this returns a large list of URLs
    
    const runningPromises = new Set();
    
    const promises = urls.map(url => limit(async () => {
    	const fetchPromise = fetch(url);
    	runningPromises.add(fetchPromise);
    
    	try {
    		return await fetchPromise;
    	} finally {
    		runningPromises.delete(fetchPromise);
    	}
    }));
    
    const shutdown = () => {
    	// Discard pending tasks (already running tasks will complete)
    	limit.clearQueue();
    
    	void Promise.allSettled([...runningPromises]).finally(() => {
    		process.exit(0);
    	});
    };
    
    for (const signal of ['SIGTERM', 'SIGINT']) {
    	process.once(signal, shutdown);
    }
    
    const results = await Promise.allSettled(promises);
  3. Adjust concurrency dynamically

    main

    You can modify limit.concurrency at runtime. This is useful for implementing backoff strategies, such as reducing concurrency when encountering HTTP 429 (Too Many Requests) errors.

    import pLimit from 'p-limit';
    
    const limit = pLimit(10);
    
    const urls = getUrls(); // Assume this returns a list of URLs
    
    async function fetchWithBackoff(url) {
    	const response = await fetch(url);
    
    	if (response.status === 429) {
    		limit.concurrency = Math.max(1, Math.floor(limit.concurrency / 2));
    	} else if (limit.concurrency < 10) {
    		limit.concurrency++;
    	}
    
    	return response;
    }
    
    const results = await limit.map(urls, fetchWithBackoff);
  4. Report progress using activeCount and pendingCount

    main

    Monitor the state of your task queue using limit.activeCount (number of currently running tasks) and limit.pendingCount (number of tasks waiting in the queue).

    import pLimit from 'p-limit';
    
    const limit = pLimit(5);
    
    const urls = getUrls(); // Assume this returns a list of URLs
    
    const progressInterval = setInterval(() => {
    	console.log(`Running: ${limit.activeCount}, pending: ${limit.pendingCount}`);
    }, 250);
    
    let results;
    
    try {
    	results = await limit.map(urls, url => fetch(url));
    } finally {
    	clearInterval(progressInterval);
    }
  5. Basic usage of p-limit

    main

    To limit concurrency, initialize pLimit with a concurrency number, then wrap your function calls in the returned limit function. This allows you to queue tasks that will execute only when the concurrency limit allows.

    import pLimit from 'p-limit';
    
    const limit = pLimit(1);
    
    const input = [
    	limit(() => fetchSomething('foo')),
    	limit(() => fetchSomething('bar')),
    	limit(() => doSomething())
    ];
    
    // Only one promise is run at once
    const result = await Promise.all(input);
    console.log(result);
  6. Fetch multiple URLs with concurrency limits

    main

    Use limit.map() to fetch multiple URLs while controlling the number of concurrent network requests.

    import pLimit from 'p-limit';
    
    const limit = pLimit(3);
    
    const urls = [
    	'https://api.example.com/users/1',
    	'https://api.example.com/users/2',
    	'https://api.example.com/users/3',
    	'https://api.example.com/users/4',
    	'https://api.example.com/users/5',
    ];
    
    const results = await limit.map(urls, async url => {
    	const response = await fetch(url);
    	return response.json();
    });
  7. Process files in batches

    main

    Use limit.map() to process a collection of items (like files from a directory) with a fixed concurrency level. This prevents overwhelming the system by limiting how many asynchronous operations run simultaneously.

    import fs from 'node:fs/promises';
    import pLimit from 'p-limit';
    
    const limit = pLimit(5);
    
    const files = await fs.readdir('uploads');
    
    const results = await limit.map(files, async file => {
    	const content = await fs.readFile(`uploads/${file}`, 'utf8');
    	return JSON.parse(content);
    });
  8. Create a reusable limited function with limitFunction()

    main

    Use the limitFunction() named export to wrap a specific function with its own concurrency management. This is ideal when you want a single function to encapsulate its own rate-limiting logic.

    import {limitFunction} from 'p-limit';
    
    const urls = getUrls(); // Assume this returns a list of URLs
    
    const fetchUrl = async url => {
    	const response = await fetch(url);
    
    	if (!response.ok) {
    		throw new Error(`Request failed with ${response.status} for ${url}`);
    	}
    
    	return response.json();
    };
    
    const limitedFetchUrl = limitFunction(fetchUrl, {concurrency: 3});
    
    const results = await Promise.all(urls.map(url => limitedFetchUrl(url)));
  9. Handle errors with partial results

    main

    To prevent one failed task from rejecting the entire batch, wrap your limit() calls inside Promise.allSettled(). This allows you to inspect each result and handle successes and failures individually.

    import pLimit from 'p-limit';
    
    const limit = pLimit(3);
    
    const urls = [
    	'https://api.example.com/users/1',
    	'https://api.example.com/users/2',
    	'https://api.example.com/users/3',
    ];
    
    const results = await Promise.allSettled(
    	urls.map(url => limit(async () => {
    		const response = await fetch(url);
    
    		if (!response.ok) {
    			throw new Error(`Request failed with ${response.status} for ${url}`);
    		}
    
    		return response.json();
    	}))
    );
    
    for (const result of results) {
    	if (result.status === 'fulfilled') {
    		console.log(result.value);
    	} else {
    		console.error(result.reason);
    	}
    }
  10. Create a limited function with limitFunction()

    main

    The named export limitFunction(fn, options) returns a new function that manages its own concurrency. This is ideal for controlling the simultaneous execution of a single specific function rather than managing a shared pool of different functions.

    import {limitFunction} from 'p-limit';
    
    const limitedFunction = limitFunction(async () => {
    	return doSomething();
    }, {concurrency: 1});
    
    const input = Array.from({length: 10}, limitedFunction);
    
    // Only one promise is run at once.
    await Promise.all(input);
  11. Initialize pLimit with concurrency options

    main

    The default export pLimit(concurrency) returns a limit function. You can pass a number or an options object.

    Options:

    • concurrency (number): The concurrency limit (minimum 1).
    • rejectOnClear (boolean): If true, pending promises are rejected with an AbortError when limit.clearQueue() is called. This is recommended when using Promise.all to prevent pending tasks from remaining unresolved.
    import pLimit from 'p-limit';
    
    const limit = pLimit({concurrency: 1});