diff --git a/lib/asyncbox.ts b/lib/asyncbox.ts index b078811..61fb840 100644 --- a/lib/asyncbox.ts +++ b/lib/asyncbox.ts @@ -1,4 +1,4 @@ -import {limitFunction} from 'p-limit'; +import type {limitFunction} from 'p-limit'; import type { CancellablePromise, @@ -11,6 +11,17 @@ import type { const LONG_SLEEP_THRESHOLD = 5000; // anything over 5000ms will turn into a spin +type PLimitModule = {limitFunction: typeof limitFunction}; + +// p-limit is ESM-only. tsc downlevels a plain `import()` in CommonJS output into a +// require()-in-a-microtask, which still hits Node's require(esm) interop and can race with a +// concurrent dynamic import() of the same module elsewhere in the process +// (ERR_REQUIRE_ESM_RACE_CONDITION). Route through `new Function` so tsc can't see or rewrite the +// import() call, keeping it a genuine native dynamic import in both the ESM and CJS builds. +const dynamicImport = new Function('specifier', 'return import(specifier)') as ( + specifier: string, +) => Promise; + /** Error thrown by {@link withTimeout} when the deadline is exceeded. */ export class TimeoutError extends Error { constructor(message?: string) { @@ -241,7 +252,7 @@ export async function asyncmap( ); } const adjustedMapper = - options === true ? mapperAsync : limitFunction(mapperAsync, {concurrency: options.concurrency}); + options === true ? mapperAsync : (await getLimitFunction())(mapperAsync, {concurrency: options.concurrency}); return Promise.all(coll.map(adjustedMapper)); } @@ -271,7 +282,7 @@ export async function asyncfilter( }, Promise.resolve([])); } const adjustedFilter = - options === true ? filterAsync : limitFunction(filterAsync, {concurrency: options.concurrency}); + options === true ? filterAsync : (await getLimitFunction())(filterAsync, {concurrency: options.concurrency}); const bools = await Promise.all(coll.map(adjustedFilter)); return coll.reduce((acc, item, i) => { if (bools[i]) { @@ -363,3 +374,9 @@ function parseSleepArg(arg: SleepArg): {ms: number; cancelError?: string | Error } throw new TypeError('sleep: expected a finite number or an object with ms'); } + +let limitFunctionPromise: Promise | undefined; +async function getLimitFunction() { + limitFunctionPromise ??= dynamicImport('p-limit'); + return (await limitFunctionPromise).limitFunction; +}