async-wrappers
Version:
A set of wrapper functions to perform debouncing, throttling, retrying etc.
114 lines (101 loc) • 2.74 kB
JavaScript
import callReduce from './callReduce';
import deferred from './deferred';
/**
* Options for [[debounce]]
*
* @typeparam Reducer an [[ArgumentsReducer]]
*
* @category Debounce
*/
/**
* Ensure multiple calls to a function will only execute it when it has been
* inactive (no more calls) for a specified delay.
*
* Execution always happens asynchronously.
*
* If the delay is `0` execution will be scheduled to occur after the current
* runtime event loop.
*
* The debounced function returns a promise that resolves to the return value
* of `func`.
*
* By default the arguments to `func` are the latest arguments given to the
* debounced function. For custom behaviour pass an `argumentsReducer`
* function.
*
* @param fn The function to debounce
* @param delay The number of milliseconds on inactivity before the function
* will be executed.
.
* @return the debounced function
*
* @category Wrapper
*/
var debounce = function debounce(fn) {
var delay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 50;
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var {
reducer,
maxDelay = 0,
maxCalls,
onCancel
} = options;
var started = 0;
var calls = 0; // Micro optimization if delay is zero defer will always force execution next tick so we can ignore maxDelay
// as all calls this process tick will be handled next tick
var afterReduce = delay > 0 && maxDelay > 0 ? () => {
if (maxCalls) {
calls++;
if (calls >= maxCalls) {
flush();
return;
}
}
if (execute.delay < 0) {
// execute is fresh reset started
started = Date.now();
execute.defer(Math.min(delay, maxDelay));
} else {
var elapsed = Date.now() - started;
if (elapsed >= maxDelay) {
// it's been too long force execution next tick
execute.defer(0);
} else {
var wait = Math.min(delay, maxDelay - elapsed);
execute.defer(wait);
}
}
} : () => {
if (maxCalls) {
calls++;
if (calls >= maxCalls) {
flush();
return;
}
}
execute.defer(delay);
};
var [call, runner, reject] = callReduce(fn, reducer, undefined, afterReduce);
var execute = deferred(() => {
calls = 0;
runner()();
});
var flush = () => {
calls = 0;
execute.cancel();
deferred(runner()).defer(0);
};
var cancel = reason => {
calls = 0;
execute.cancel();
if (onCancel) {
onCancel();
}
reject(reason ? reason : new Error('cancelled'));
};
var debounced = call;
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
};
export default debounce;