I'am trying to implement something like autocomplete, so I'am running the function when oninput event fires. Because I'am making a fetch request instead of running it on every change I'd like to run it not more than once in (maybe) 500ms. Is there a way to do this?
<body>
<input id="input" type="text">
<script>
function filterData(substr) {
fetch(url)
.then(response => response.json())
.then(data => {
let filteredData = data.filter(person => person.name.includes(substr));
print(filteredData);
})
}
document.getElementById("input").oninput = (e) => filterData(e.target.value);
</script>
</body>
The OP's description resembles a throttled behavior. There are various libraries/implementations available in order to achieve both the throttled process and its sibling the debounced process. Commonly used are the methods of lodash and/or underscore.js.
The next provided example code uses a mocked version of the OP's originally provided filterData
. It shows the different behaviors of throttle and debounce for lodash and of two basic custom throttle
/debounce
implementations ...
/*function filterData(substr) {
fetch(url)
.then(response => response.json())
.then(data => {
let filteredData = data.filter(person => person.name.includes(substr));
print(filteredData);
});
}*/
function filterData(substr) {
new Promise((resolve/*, reject*/) => {
setTimeout(() => {
resolve({
search: substr,
matches: [substr + 'Bar', substr + ' bazz', substr + ' Foooo']
});
}, 300);
}).then(response => console.log({ response }));
}
function handleSearch(evt) {
// console.log('this :', this);
// // return filterData(this.value);
return filterData(evt.target.value);
}
document
.querySelector("#basicThrottled")
.addEventListener('input', basicThrottle(handleSearch, 500));
document
.querySelector("#basicDebounced")
.addEventListener('input', basicDebounce(handleSearch, 500));
document
.querySelector("#lodashThrottled")
.addEventListener('input', _.throttle(handleSearch, 500));
document
.querySelector("#lodashDebounced")
.addEventListener('input', _.debounce(handleSearch, 500));
body { margin: 0; }
[type="search"] { min-width: 24%; max-width: 24%; }
.as-console-wrapper { min-height: 85%; }
<input id="basicThrottled" type="search" placeholder="basic throttled ..." />
<input id="basicDebounced" type="search" placeholder="basic debounced ..." />
<input id="lodashThrottled" type="search" placeholder="lodash throttled ..." />
<input id="lodashDebounced" type="search" placeholder="lodash debounced ..." />
<script>
function basicDebounce(proceed, delay = 300, target) {
let timeoutId = null;
return function debounced(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(proceed.bind(target ?? this), delay, ...args);
};
}
function basicThrottle(proceed, threshold = 200, target) {
let timeoutId = null;
let referenceTime = 0;
return function throttled(...args) {
const currentTime = Date.now();
if (currentTime - referenceTime >= threshold) {
clearTimeout(timeoutId);
referenceTime = currentTime;
const trigger = proceed.bind((target ?? this), ...args);
timeoutId = setTimeout((() => {
referenceTime = 0;
trigger();
}), threshold);
trigger();
}
};
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>