hfs
Version:
HTTP File Server
84 lines (83 loc) • 3.83 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
Object.defineProperty(exports, "__esModule", { value: true });
exports.debounceAsync = debounceAsync;
exports.singleWorkerFromBatchWorker = singleWorkerFromBatchWorker;
// like lodash.debounce, but also avoids async invocations to overlap
function debounceAsync(
// the function you want to not call too often, too soon
callback, options = {}) {
const { wait = 0, leading = false, maxWait = Infinity, cancelable = false, retain = 0, retainFailure, reuseRunning } = options;
let started = 0; // latest callback invocation
let runningCallback; // latest callback invocation result
let latestDebouncer; // latest wrapper invocation
let waitingSince = 0; // we are delaying invocation since
let whoIsWaiting; // args object identifies the pending instance, and incidentally stores args
let latestCallback;
let latestHasFailed = false;
let latestTimestamp = 0;
const interceptingWrapper = (...args) => latestDebouncer = debouncer(...args);
return Object.assign(interceptingWrapper, {
clearRetain: () => latestCallback = undefined,
flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
isWorking: () => runningCallback,
...cancelable && {
cancel() {
waitingSince = 0;
whoIsWaiting = undefined;
}
}
});
async function debouncer(...args) {
if (reuseRunning && runningCallback)
return runningCallback;
const now = Date.now();
if (latestCallback && now - latestTimestamp < (latestHasFailed ? retainFailure !== null && retainFailure !== void 0 ? retainFailure : retain : retain))
return await latestCallback;
whoIsWaiting = args;
waitingSince || (waitingSince = now);
const waitingCap = maxWait - (now - (waitingSince || started));
const waitFor = Math.min(waitingCap, leading ? wait - (now - started) : wait);
if (waitFor > 0)
await new Promise(resolve => setTimeout(resolve, waitFor));
if (!whoIsWaiting) { // canceled
waitingSince = 0;
return undefined;
}
if (whoIsWaiting !== args) // another fresher call is waiting
return latestDebouncer;
await runningCallback; // in case we don't reuseRunning
return exec();
}
async function exec() {
if (!whoIsWaiting)
return undefined;
waitingSince = 0;
started = Date.now();
try {
const args = whoIsWaiting;
whoIsWaiting = undefined;
runningCallback = Promise.resolve(callback(...args)); // cast to promise, in case callback was not really async (or hybrid)
runningCallback.then(() => latestHasFailed = false, () => latestHasFailed = true);
return await runningCallback; // await necessary to go-finally at the right time and even on exceptions
}
finally {
latestCallback = runningCallback;
latestTimestamp = Date.now();
runningCallback = undefined;
}
}
}
// given a function that works on a batch of requests, returns the function that works on a single request
function singleWorkerFromBatchWorker(batchWorker, { maxWait = Infinity } = {}) {
let batch = [];
const debounced = debounceAsync(async () => {
const ret = batchWorker(batch);
batch = []; // this is reset as batchWorker starts, but without waiting
return ret;
}, { wait: 100, maxWait });
return (...args) => {
const idx = batch.push(args) - 1;
return debounced().then((x) => x[idx]);
};
}
;