Skip to content

Commit ad8afe6

Browse files
authored
Fix AsyncResource propagation issue (#71)
1 parent 38a6773 commit ad8afe6

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

async-hooks-stub.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const AsyncResource = {
2+
bind(fn, _type, thisArg) {
3+
return fn.bind(thisArg);
4+
},
5+
};
6+
7+
export class AsyncLocalStorage {
8+
getStore() {
9+
return undefined;
10+
}
11+
12+
run(_store, callback) {
13+
return callback();
14+
}
15+
}

index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Queue from 'yocto-queue';
2+
import {AsyncResource} from '#async_hooks';
23

34
export default function pLimit(concurrency) {
45
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
@@ -31,7 +32,9 @@ export default function pLimit(concurrency) {
3132
};
3233

3334
const enqueue = (fn, resolve, args) => {
34-
queue.enqueue(run.bind(undefined, fn, resolve, args));
35+
queue.enqueue(
36+
AsyncResource.bind(run.bind(undefined, fn, resolve, args)),
37+
);
3538

3639
(async () => {
3740
// This function needs to wait until the next microtask before comparing

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
},
1313
"type": "module",
1414
"exports": "./index.js",
15+
"imports": {
16+
"#async_hooks": {
17+
"node": "async_hooks",
18+
"default": "./async-hooks-stub.js"
19+
}
20+
},
1521
"engines": {
1622
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1723
},
@@ -20,7 +26,8 @@
2026
},
2127
"files": [
2228
"index.js",
23-
"index.d.ts"
29+
"index.d.ts",
30+
"async-hooks-stub.js"
2431
],
2532
"keywords": [
2633
"promise",

test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import delay from 'delay';
33
import inRange from 'in-range';
44
import timeSpan from 'time-span';
55
import randomInt from 'random-int';
6+
import {AsyncLocalStorage} from '#async_hooks';
67
import pLimit from './index.js';
78

89
test('concurrency: 1', async t => {
@@ -40,6 +41,23 @@ test('concurrency: 4', async t => {
4041
await Promise.all(input);
4142
});
4243

44+
test('propagates async execution context properly', async t => {
45+
const concurrency = 2;
46+
const limit = pLimit(concurrency);
47+
const store = new AsyncLocalStorage();
48+
49+
const checkId = async id => {
50+
await Promise.resolve();
51+
t.is(id, store.getStore()?.id);
52+
};
53+
54+
const startContext = async id => store.run({id}, () => limit(checkId, id));
55+
56+
await Promise.all(
57+
Array.from({length: 100}, (_, id) => startContext(id)),
58+
);
59+
});
60+
4361
test('non-promise returning function', async t => {
4462
await t.notThrowsAsync(async () => {
4563
const limit = pLimit(1);

0 commit comments

Comments
 (0)