Skip to content

Commit

Permalink
Improve performance (#83)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
sugoroku-y and sindresorhus authored Jul 4, 2024
1 parent f53bdb5 commit 80273d7
Show file tree
Hide file tree
Showing 4 changed files with 19 additions and 33 deletions.
15 changes: 0 additions & 15 deletions async-hooks-stub.js

This file was deleted.

29 changes: 18 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
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)) {
Expand All @@ -9,17 +8,21 @@ export default function pLimit(concurrency) {
const queue = new Queue();
let activeCount = 0;

const next = () => {
activeCount--;

const resumeNext = () => {
if (queue.size > 0) {
queue.dequeue()();
// Since `pendingCount` has been decreased by one, increase `activeCount` by one.
activeCount++;
}
};

const run = async (function_, resolve, arguments_) => {
activeCount++;
const next = () => {
activeCount--;

resumeNext();
};

const run = async (function_, resolve, arguments_) => {
const result = (async () => function_(...arguments_))();

resolve(result);
Expand All @@ -32,19 +35,23 @@ export default function pLimit(concurrency) {
};

const enqueue = (function_, resolve, arguments_) => {
queue.enqueue(
AsyncResource.bind(run.bind(undefined, function_, resolve, arguments_)),
// Queue `internalResolve` instead of the `run` function
// to preserve asynchronous context.
new Promise(internalResolve => {
queue.enqueue(internalResolve);
}).then(
run.bind(undefined, function_, resolve, arguments_),
);

(async () => {
// This function needs to wait until the next microtask before comparing
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
// when the run function is dequeued and called. The comparison in the if-statement
// after the `internalResolve` function is dequeued and called. The comparison in the if-statement
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
await Promise.resolve();

if (activeCount < concurrency && queue.size > 0) {
queue.dequeue()();
if (activeCount < concurrency) {
resumeNext();
}
})();
};
Expand Down
6 changes: 0 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@
"types": "./index.d.ts",
"default": "./index.js"
},
"imports": {
"#async_hooks": {
"node": "async_hooks",
"default": "./async-hooks-stub.js"
}
},
"engines": {
"node": ">=18"
},
Expand Down
2 changes: 1 addition & 1 deletion test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {AsyncLocalStorage} from 'node:async_hooks';
import test from 'ava';
import delay from 'delay';
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 = [
Expand Down

0 comments on commit 80273d7

Please sign in to comment.