Files
bitvid/js/utils/asyncUtils.js
T
google-labs-jules[bot]andPR0M3TH3AN 004c0bed83 fix(ci): reduce channelProfile.js size and bound relay concurrency
- Refactored `js/channelProfile.js` to use `pMapSettled` from `js/utils/asyncUtils.js`, fixing both the concurrency issue (P1) and the file size lint failure.
- `pMapSettled` replaces a bulky inline `pMap` wrapper, reducing code volume.
- Ensures `js/channelProfile.js` stays within its grandfathered line count limit (5579 lines).

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-17 10:55:28 +00:00

131 lines
3.4 KiB
JavaScript

import { devLogger } from "./logger.js";
/**
* Maps over an iterable with a concurrency limit.
*
* @param {Iterable} iterable - The items to map over.
* @param {Function} mapper - The async mapping function.
* @param {Object} options - Options object.
* @param {number} options.concurrency - The concurrency limit (default: Infinity).
* @returns {Promise<Array>} - The results of the mapping function in original order.
*/
export async function pMap(iterable, mapper, { concurrency = Infinity } = {}) {
const results = [];
const iterator = iterable[Symbol.iterator]();
let index = 0;
let active = 0;
let done = false;
let rejected = false;
return new Promise((resolve, reject) => {
function next() {
if (rejected) return;
if (done && active === 0) {
resolve(results);
return;
}
while (active < concurrency && !done) {
const nextItem = iterator.next();
if (nextItem.done) {
done = true;
if (active === 0) resolve(results);
return;
}
const currentIndex = index++;
active++;
Promise.resolve(nextItem.value)
.then((value) => mapper(value, currentIndex))
.then((result) => {
results[currentIndex] = result;
active--;
next();
})
.catch((err) => {
rejected = true;
reject(err);
});
}
}
next();
});
}
/**
* Similar to pMap, but returns { status: 'fulfilled', value } or { status: 'rejected', reason }
* for each item, ensuring all items are processed (like Promise.allSettled).
*
* @param {Iterable} iterable - The items to map over.
* @param {Function} mapper - The async mapping function.
* @param {Object} options - Options object.
* @param {number} options.concurrency - The concurrency limit (default: Infinity).
* @returns {Promise<Array>} - The settled results.
*/
export async function pMapSettled(iterable, mapper, options) {
return pMap(
iterable,
async (item, index) => {
try {
const value = await mapper(item, index);
return { status: "fulfilled", value };
} catch (reason) {
return { status: "rejected", reason };
}
},
options
);
}
export function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request timed out") {
const resolvedTimeout = Number(timeoutMs);
const effectiveTimeout =
Number.isFinite(resolvedTimeout) && resolvedTimeout > 0
? Math.floor(resolvedTimeout)
: 4000;
let timeoutId = null;
let settled = false;
return new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
if (settled) {
return;
}
settled = true;
if (typeof onTimeout === "function") {
try {
onTimeout();
} catch (cleanupError) {
devLogger.warn("[asyncUtils] Timeout cleanup failed:", cleanupError);
}
}
reject(new Error(message));
}, effectiveTimeout);
Promise.resolve(promise)
.then((value) => {
if (settled) {
return;
}
settled = true;
if (timeoutId) {
clearTimeout(timeoutId);
}
resolve(value);
})
.catch((error) => {
if (settled) {
return;
}
settled = true;
if (timeoutId) {
clearTimeout(timeoutId);
}
reject(error);
});
});
}