UNPKG

hfs

Version:
84 lines (83 loc) 3.83 kB
"use strict"; // 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]); }; }