mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-02-02 00:34:00 +01:00
346111250e
Before this change, `promises.timesLimit()` created `concurrency - 1` too many promises. The only users of this function use a concurrency of 500, so this meant that 499 extra promises were created each time it was used. The bug didn't affect correctness, but it did result in a large number of unnecessary database operations whenever a pad was deleted. This change fixes that bug. Also: * Convert the function to async and have it resolve after all of the created promises are resolved. * Reject concurrency of 0 (unless total is 0). * Document the function. * Add tests.
55 lines
3.2 KiB
JavaScript
55 lines
3.2 KiB
JavaScript
/**
|
|
* Helpers to manipulate promises (like async but for promises).
|
|
*/
|
|
|
|
// Returns a Promise that resolves to the first resolved value from `promises` that satisfies
|
|
// `predicate`. Resolves to `undefined` if none of the Promises satisfy `predicate`, or if
|
|
// `promises` is empty. If `predicate` is nullish, the truthiness of the resolved value is used as
|
|
// the predicate.
|
|
exports.firstSatisfies = (promises, predicate) => {
|
|
if (predicate == null) predicate = (x) => x;
|
|
|
|
// Transform each original Promise into a Promise that never resolves if the original resolved
|
|
// value does not satisfy `predicate`. These transformed Promises will be passed to Promise.race,
|
|
// yielding the first resolved value that satisfies `predicate`.
|
|
const newPromises = promises.map(
|
|
(p) => new Promise((resolve, reject) => p.then((v) => predicate(v) && resolve(v), reject)));
|
|
|
|
// If `promises` is an empty array or if none of them resolve to a value that satisfies
|
|
// `predicate`, then `Promise.race(newPromises)` will never resolve. To handle that, add another
|
|
// Promise that resolves to `undefined` after all of the original Promises resolve.
|
|
//
|
|
// Note: If all of the original Promises simultaneously resolve to a value that satisfies
|
|
// `predicate` (perhaps they were already resolved when this function was called), then this
|
|
// Promise will resolve too, and with a value of `undefined`. There is no concern that this
|
|
// Promise will win the race and thus cause an erroneous `undefined` result. This is because
|
|
// a resolved Promise's `.then()` function is scheduled for execution -- not executed right away
|
|
// -- and ES guarantees in-order execution of the enqueued invocations. Each of the above
|
|
// transformed Promises has a `.then()` chain of length one, while the Promise added here has a
|
|
// `.then()` chain of length two or more (at least one `.then()` that is internal to
|
|
// `Promise.all()`, plus the `.then()` function added here). By the time the `.then()` function
|
|
// added here executes, all of the above transformed Promises will have already resolved and one
|
|
// will have been chosen as the winner.
|
|
newPromises.push(Promise.all(promises).then(() => {}));
|
|
|
|
return Promise.race(newPromises);
|
|
};
|
|
|
|
// Calls `promiseCreator(i)` a total number of `total` times, where `i` is 0 through `total - 1` (in
|
|
// order). The `concurrency` argument specifies the maximum number of Promises returned by
|
|
// `promiseCreator` that are allowed to be active (unresolved) simultaneously. (In other words: If
|
|
// `total` is greater than `concurrency`, then `concurrency` Promises will be created right away,
|
|
// and each remaining Promise will be created once one of the earlier Promises resolves.) This async
|
|
// function resolves once all `total` Promises have resolved.
|
|
exports.timesLimit = async (total, concurrency, promiseCreator) => {
|
|
if (total > 0 && concurrency <= 0) throw new RangeError('concurrency must be positive');
|
|
let next = 0;
|
|
const addAnother = () => promiseCreator(next++).finally(() => {
|
|
if (next < total) return addAnother();
|
|
});
|
|
const promises = [];
|
|
for (var i = 0; i < concurrency && i < total; i++) {
|
|
promises.push(addAnother());
|
|
}
|
|
await Promise.all(promises);
|
|
}
|