diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 441975c..346585c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,10 +10,11 @@ jobs: fail-fast: false matrix: node-version: - - 16 + - 20 + - 18 steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/async-hooks-stub.js b/async-hooks-stub.js new file mode 100644 index 0000000..913c7c6 --- /dev/null +++ b/async-hooks-stub.js @@ -0,0 +1,15 @@ +export const AsyncResource = { + bind(fn, _type, thisArg) { + return fn.bind(thisArg); + }, +}; + +export class AsyncLocalStorage { + getStore() { + return undefined; + } + + run(_store, callback) { + return callback(); + } +} diff --git a/index.d.ts b/index.d.ts index caae030..303c7d9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/member-ordering */ - -export interface LimitFunction { +export type LimitFunction = { /** The number of promises that are currently running. */ @@ -26,10 +24,10 @@ export interface LimitFunction { @returns The promise returned by calling `fn(...arguments)`. */ ( - fn: (...arguments: Arguments) => PromiseLike | ReturnType, - ...arguments: Arguments + fn: (...arguments_: Arguments) => PromiseLike | ReturnType, + ...arguments_: Arguments ): Promise; -} +}; /** Run multiple promise-returning & async functions with limited concurrency. diff --git a/index.js b/index.js index b54b99b..4a5a0f0 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ import Queue from 'yocto-queue'; +import {AsyncResource} from '#async_hooks'; export default function pLimit(concurrency) { if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) { @@ -16,10 +17,10 @@ export default function pLimit(concurrency) { } }; - const run = async (fn, resolve, args) => { + const run = async (function_, resolve, arguments_) => { activeCount++; - const result = (async () => fn(...args))(); + const result = (async () => function_(...arguments_))(); resolve(result); @@ -30,8 +31,10 @@ export default function pLimit(concurrency) { next(); }; - const enqueue = (fn, resolve, args) => { - queue.enqueue(run.bind(undefined, fn, resolve, args)); + const enqueue = (function_, resolve, arguments_) => { + queue.enqueue( + AsyncResource.bind(run.bind(undefined, function_, resolve, arguments_)), + ); (async () => { // This function needs to wait until the next microtask before comparing @@ -46,8 +49,8 @@ export default function pLimit(concurrency) { })(); }; - const generator = (fn, ...args) => new Promise(resolve => { - enqueue(fn, resolve, args); + const generator = (function_, ...arguments_) => new Promise(resolve => { + enqueue(function_, resolve, arguments_); }); Object.defineProperties(generator, { @@ -58,7 +61,7 @@ export default function pLimit(concurrency) { get: () => queue.size, }, clearQueue: { - value: () => { + value() { queue.clear(); }, }, diff --git a/package.json b/package.json index 2e41e6e..6d5a9b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p-limit", - "version": "4.0.0", + "version": "5.0.0", "description": "Run multiple promise-returning & async functions with limited concurrency", "license": "MIT", "repository": "sindresorhus/p-limit", @@ -11,16 +11,26 @@ "url": "https://sindresorhus.com" }, "type": "module", - "exports": "./index.js", + "exports": { + "types": "./index.d.ts", + "default": "./index.js" + }, + "imports": { + "#async_hooks": { + "node": "async_hooks", + "default": "./async-hooks-stub.js" + } + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "scripts": { "test": "xo && ava && tsd" }, "files": [ "index.js", - "index.d.ts" + "index.d.ts", + "async-hooks-stub.js" ], "keywords": [ "promise", @@ -43,12 +53,12 @@ "yocto-queue": "^1.0.0" }, "devDependencies": { - "ava": "^3.15.0", - "delay": "^5.0.0", + "ava": "^5.3.1", + "delay": "^6.0.0", "in-range": "^3.0.0", "random-int": "^3.0.0", - "time-span": "^5.0.0", - "tsd": "^0.17.0", - "xo": "^0.44.0" + "time-span": "^5.1.0", + "tsd": "^0.29.0", + "xo": "^0.56.0" } } diff --git a/readme.md b/readme.md index b02253b..4e890f8 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,12 @@ > Run multiple promise-returning & async functions with limited concurrency +*Works in Node.js and browsers.* + ## Install -``` -$ npm install p-limit +```sh +npm install p-limit ``` ## Usage @@ -80,20 +82,7 @@ This package is only about limiting the number of concurrent executions, while ` ## Related -- [p-queue](https://github.com/sindresorhus/p-queue) - Promise queue with concurrency control - [p-throttle](https://github.com/sindresorhus/p-throttle) - Throttle promise-returning & async functions - [p-debounce](https://github.com/sindresorhus/p-debounce) - Debounce promise-returning & async functions - [p-all](https://github.com/sindresorhus/p-all) - Run promise-returning & async functions concurrently with optional limited concurrency - [More…](https://github.com/sindresorhus/promise-fun) - ---- - -
- - Get professional support for this package with a Tidelift subscription - -
- - Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. -
-
diff --git a/test.js b/test.js index fd61a97..6477920 100644 --- a/test.js +++ b/test.js @@ -4,6 +4,7 @@ import inRange from 'in-range'; import timeSpan from 'time-span'; import randomInt from 'random-int'; import pLimit from './index.js'; +import {AsyncLocalStorage} from '#async_hooks'; test('concurrency: 1', async t => { const input = [ @@ -40,6 +41,23 @@ test('concurrency: 4', async t => { await Promise.all(input); }); +test('propagates async execution context properly', async t => { + const concurrency = 2; + const limit = pLimit(concurrency); + const store = new AsyncLocalStorage(); + + const checkId = async id => { + await Promise.resolve(); + t.is(id, store.getStore()?.id); + }; + + const startContext = async id => store.run({id}, () => limit(checkId, id)); + + await Promise.all( + Array.from({length: 100}, (_, id) => startContext(id)), + ); +}); + test('non-promise returning function', async t => { await t.notThrowsAsync(async () => { const limit = pLimit(1);