Wait until a condition is true? - javascript

I'm using navigator.geolocation.watchPosition in JavaScript, and I want a way to deal with the possibility that the user might submit a form relying on location before watchPosition has found its location.
Ideally the user would see a 'Waiting for location' message periodically until the location was obtained, then the form would submit.
However, I'm not sure how to implement this in JavaScript given its lack of a wait function.
Current code:
var current_latlng = null;
function gpsSuccess(pos){
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
}
else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {timeout:5000, maximumAge: 300000});
$('#route-form').submit(function(event) {
// User submits form, we need their location...
while(current_location==null) {
toastMessage('Waiting for your location...');
wait(500); // What should I use instead?
}
// Continue with location found...
});

Modern solution using Promise
function waitFor(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Usage
waitFor(_ => flag === true)
.then(_ => console.log('the wait is over!'));
or
async function demo() {
await waitFor(_ => flag === true);
console.log('the wait is over!');
}
References
Promises
Arrow Functions
Async/Await

Personally, I use a waitfor() function which encapsulates a setTimeout():
//**********************************************************************
// function waitfor - Wait until a condition is met
//
// Needed parameters:
// test: function that returns a value
// expectedValue: the value of the test function we are waiting for
// msec: delay between the calls to test
// callback: function to execute when the condition is met
// Parameters for debugging:
// count: used to count the loops
// source: a string to specify an ID, a message, etc
//**********************************************************************
function waitfor(test, expectedValue, msec, count, source, callback) {
// Check if condition met. If not, re-check later (msec).
while (test() !== expectedValue) {
count++;
setTimeout(function() {
waitfor(test, expectedValue, msec, count, source, callback);
}, msec);
return;
}
// Condition finally met. callback() can be executed.
console.log(source + ': ' + test() + ', expected: ' + expectedValue + ', ' + count + ' loops.');
callback();
}
I use my waitfor() function in the following way:
var _TIMEOUT = 50; // waitfor test rate [msec]
var bBusy = true; // Busy flag (will be changed somewhere else in the code)
...
// Test a flag
function _isBusy() {
return bBusy;
}
...
// Wait until idle (busy must be false)
waitfor(_isBusy, false, _TIMEOUT, 0, 'play->busy false', function() {
alert('The show can resume !');
});

This is precisely what promises were invented and implemented (since OP asked his question) for.
See all of the various implementations, eg promisejs.org

You'll want to use setTimeout:
function checkAndSubmit(form) {
var location = getLocation();
if (!location) {
setTimeout(checkAndSubmit, 500, form); // setTimeout(func, timeMS, params...)
} else {
// Set location on form here if it isn't in getLocation()
form.submit();
}
}
... where getLocation looks up your location.

You could use a timeout to try to re-submit the form:
$('#route-form').submit(function(event) {
// User submits form, we need their location...
if(current_location==null) {
toastMessage('Waiting for your location...');
setTimeout(function(){ $('#route-form').submit(); }, 500); // Try to submit form after timeout
return false;
} else {
// Continue with location found...
}
});

export default (condition: Function, interval = 1000) =>
new Promise((resolve) => {
const runner = () => {
const timeout = setTimeout(runner, interval);
if (condition()) {
clearTimeout(timeout);
resolve(undefined);
return;
}
};
runner();
});

class App extends React.Component {
componentDidMount() {
this.processToken();
}
processToken = () => {
try {
const params = querySearch(this.props.location.search);
if('accessToken' in params){
this.setOrderContext(params);
this.props.history.push(`/myinfo`);
}
} catch(ex) {
console.log(ex);
}
}
setOrderContext (params){
//this action calls a reducer and put the token in session storage
this.props.userActions.processUserToken({data: {accessToken:params.accessToken}});
}
render() {
return (
<Switch>
//myinfo component needs accessToken to retrieve my info
<Route path="/myInfo" component={InofUI.App} />
</Switch>
);
}
And then inside InofUI.App
componentDidMount() {
this.retrieveMyInfo();
}
retrieveMyInfo = async () => {
await this.untilTokenIsSet();
const { location, history } = this.props;
this.props.processUser(location, history);
}
untilTokenIsSet= () => {
const poll = (resolve) => {
const { user } = this.props;
const { accessToken } = user;
console.log('getting accessToken', accessToken);
if (accessToken) {
resolve();
} else {
console.log('wating for token .. ');
setTimeout(() => poll(resolve), 100);
}
};
return new Promise(poll);
}

Try using setInterval and clearInterval like this...
var current_latlng = null;
function gpsSuccess(pos) {
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
} else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {
timeout: 5000,
maximumAge: 300000
});
$('#route-form').submit(function (event) {
// User submits form, we need their location...
// Checks status every half-second
var watch = setInterval(task, 500)
function task() {
if (current_latlng != null) {
clearInterval(watch)
watch = false
return callback()
} else {
toastMessage('Waiting for your location...');
}
}
function callback() {
// Continue on with location found...
}
});

This accepts any function, even if it's async, and when it evaluates to a truthy value (checking every quarter-second by default), resolves to it.
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}
Example
example();
async function example() {
let foo = 'bar';
setTimeout(() => foo = null, 2000);
console.log(`foo === "${foo}"`);
await waitFor(() => foo === null);
console.log('2 seconds have elapsed.');
console.log(`foo === ${foo}`);
}
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}

Related

TomTom API - Input has value but remains untouched

I'm using the TomTom FuzzySearch/Autocomplete API, to allow users to search for an address on a form and then the address input fields will prepopulate (with the address values), when the user selects an address.
My API call works absolutely fine and the input fields display the correct values.
The problem I have, is the input fields remain untouched, despite the fields having a value. (If I type document.getElementById("house-number-textbox").value, a value is returned).
How can I turned the input fields to be touched, when the input values are applied?
I noticed, if I type something in the input field, after my input values have been added, only then does the form register my inputs as valid/touched.
PS - The reason I am injecting my scripts is because I'm using an AB Testing tool. So despite the form/application being AngularJS, I can only manipulate the form via the AB Testing tool, (on top of the compiled codebase - hence why I'm using vanilla JS).
Here's my code:
function waitForElement(className, callBack){
window.setTimeout(function(){
var element = document.getElementById(className);
if(element) {
callBack(className, element);
} else {
waitForElement(className, callBack);
}
},2000)
};
// LOAD API SCRIPTS
function loadScript(scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl;
document.body.appendChild(script);
return new Promise((res, rej) => {
script.onload = function() {
res();
}
script.onerror = function () {
rej();
}
});
};
// RESULTS MANAGER
function appendParentSelector(parentSelector, selector) {
return parentSelector ? parentSelector + ' ' + selector : selector;
}
function ResultsManager(resultsElementSelector) {
this.resultsElement = document.querySelector(appendParentSelector(resultsElementSelector, '.js-results'));
this.resultsPlaceholder =
document.querySelector(appendParentSelector(resultsElementSelector, '.js-results-placeholder'));
this.resultsLoader = document.querySelector(appendParentSelector(resultsElementSelector, '.js-results-loader'));
}
ResultsManager.prototype.loading = function() {
this.resultsLoader.removeAttribute('hidden');
this.resultsElement.setAttribute('hidden', 'hidden');
this.resultsPlaceholder.setAttribute('hidden', 'hidden');
this.resultsElement.innerHTML = '';
};
ResultsManager.prototype.success = function() {
this.resultsLoader.setAttribute('hidden', 'hidden');
this.resultsElement.removeAttribute('hidden');
};
ResultsManager.prototype.resultsNotFound = function() {
this.resultsElement.setAttribute('hidden', 'hidden');
this.resultsLoader.setAttribute('hidden', 'hidden');
this.resultsPlaceholder.removeAttribute('hidden');
};
ResultsManager.prototype.append = function(element) {
this.resultsElement.appendChild(element);
};
ResultsManager.prototype.clear = function() {
for (var i = 0; i < this.resultsElement.children.length; i++) {
this.resultsElement.removeChild(this.resultsElement.children[i]);
}
};
waitForElement("house-number-textbox",function(){
console.log("WAIT FOR ELEMENT DONE");
window.ResultsManager = window.ResultsManager || ResultsManager;
console.log("document.getElementById(component)", document.getElementById("house-number-textbox") );
// use
loadScript('https://api.tomtom.com/maps-sdk-for-web/cdn/plugins/SearchBox/2.24.2//SearchBox-web.js')
.then(() => {
console.log('Script loaded!');
})
.catch(() => {
console.error('Script loading failed! Handle this error');
});
// use
loadScript('https://api.tomtom.com/maps-sdk-for-web/cdn/5.x/5.64.0/services/services-web.min.js')
.then(() => {
console.log('Script loaded!');
// HANDLE RESULTS
tt.setProductInfo('ABTest', '1');
// Options for the fuzzySearch service
var searchOptions = {
key: 'XXX',
language: 'en-Gb',
limit: 10,
countrySet: "GB"
};
var searchBoxOptions = {
minNumberOfCharacters: 1,
searchOptions: searchOptions
// autocompleteOptions: autocompleteOptions
};
var ttSearchBox = new tt.plugins.SearchBox(tt.services, searchBoxOptions);
document.querySelector('.tt-side-panel__header').appendChild(ttSearchBox.getSearchBoxHTML());
let componentForm = {
// streetName: "house-number-textbox",
municipalitySubdivision: "street-name-textbox",
localName: "town-city-textbox",
extendedPostalCode: "postcode-textbox"
};
function SidePanel(selector) {
// this.map = map;
this.element = document.querySelector(selector);
}
new SidePanel('.tt-side-panel');
var resultsManager = new ResultsManager();
ttSearchBox.on('tomtom.searchbox.resultscleared', handleResultsCleared);
ttSearchBox.on('tomtom.searchbox.resultsfound', handleResultsFound);
ttSearchBox.on('tomtom.searchbox.resultfocused', handleResultSelection);
ttSearchBox.on('tomtom.searchbox.resultselected', handleResultSelection);
function handleResultsCleared() {
resultsManager.clear();
}
// HANDLE RESULST
function handleResultsFound(event) {
// Display fuzzySearch results if request was triggered by pressing enter
if (event.data.results && event.data.results.fuzzySearch && event.data.metadata.triggeredBy === 'submit') {
var results = event.data.results.fuzzySearch.results;
if (results.length === 0) {
handleNoResults();
}
resultsManager.success();
console.log("results", results);
}
if (event.data.errors) {
console("event.data.errors", event.data.errors);
}
}
// RESPONSE
function handleResultSelection(event) {
if (isFuzzySearchResult(event)) {
// Display selected result on the map
var result = event.data.result;
console.log("THIS result", result);
;
resultsManager.success();
for (const component in componentForm) {
console.log("componentForm", componentForm);
document.getElementById(componentForm[component]).value = result.address[component];
document.getElementById(componentForm[component]).disabled = false;
console.log('component', componentForm[component]);
if (document.getElementById(componentForm[component]).value === 'undefined') {
document.getElementById(componentForm[component]).value = "";
}
};
if (result.address.streetNumber) {
document.getElementById("house-number-textbox").value = result.address.streetNumber + " " + result.address.streetName;
} else {
document.getElementById("house-number-textbox").value = result.address.streetName;
};
};
}
function isFuzzySearchResult(event) {
return !('matches' in event.data.result);
}
function handleNoResults() {
resultsManager.clear();
resultsManager.resultsNotFound();
searchMarkersManager.clear();
infoHint.setMessage(
'No results for "' +
ttSearchBox.getValue() +
'" found nearby. Try changing the viewport.'
);
};
document.querySelector(".tt-search-box-input").setAttribute("placeholder", "Enter your address...");
})
.catch(() => {
console.error('Script loading failed! Handle this error');
});
});

set interval inside a function must return resolve message or reject

I am trying to make a timer, and trying to exit from the callback hell that is happening inside the code I am writing, this is a part from the code I have written, the problem is that I am not receiving the feedback from the promise, so no resolve message, no reject message.
what is the problem?
var timer = 15;
var startTimer = (timer) => {
return new Promise((resolve, reject)=>{
if(resolve){
var countDown = setInterval(() => {
console.log(timer);
timer--;
if (timer == 0) {
clearInterval(countDown);
return "YOU ARE LOSER";
}
}, 1000);
}
if(reject){
return "sorry something went wrong!";
}
})
}
startTimer(timer)
.then(message =>{
console.log(message);
//the message should be "You are loser!".
})
.catch(message =>{
console.log(message);
})
resolve and reject are functions to call. resolve is for when the asynchronous operation has completed, and reject for when an error has occurred. When invoking these functions, you can supply a value to propagate along the promise chain.
var count = 5
var startTimer = (count)=>{
return new Promise((resolve,reject)=>{
try {
var intervalId = setInterval(()=>{
if(count) {
console.log(count--)
return
}
clearInterval(intervalId)
resolve( "YOU ARE LOSER")
}, 1000)
} catch {
reject('sorry something went wrong!')
}
})
}
startTimer(count)
.then((result)=>{ console.log(result) })
.catch((err)=>{ console.log(err) })
You're using promises wrong, take a look at this friendly tutorial.
You resolve or reject the value that you want to return.
var timer = 15;
var startTimer = (timer) => {
return new Promise((resolve, reject) => {
try {
var countDown = setInterval(() => {
console.log(timer);
timer--;
if (timer == 0) {
clearInterval(countDown);
resolve("YOU ARE LOSER");
}
}, 1000);
} catch (err) {
reject("sorry something went wrong!");
}
})
}
startTimer(timer)
.then(message => {
console.log(message);
})
.catch(message => {
console.log(message);
})
Simplified version without promises
var timer = 10;
var countDown = 0;
var startTimer = fn => setInterval(() => {
console.log(timer);
if (--timer === 0)
{
clearInterval(countDown);
console.log("You lose");
if(fn)
fn();
}
}, 500);
function onTimerFinished() {
console.log("Timer has finished");
}
try {
countDown = startTimer(onTimerFinished);
} catch (err) {
console.error(err);
}

How to implement the lodash _.throttle in vanilla javascript? [duplicate]

I am looking for a simple throttle in JavaScript. I know libraries like lodash and underscore have it, but only for one function it will be overkill to include any of those libraries.
I was also checking if jQuery has a similar function - could not find.
I have found one working throttle, and here is the code:
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 250);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
The problem with this is: it fires the function once more after the throttle time is complete. So let's assume I made a throttle that fires every 10 seconds on keypress - if I do keypress 2 times, it will still fire the second keypress when 10 seconds are completed. I do not want this behavior.
I would use the underscore.js or lodash source code to find a well tested version of this function.
Here is the slightly modified version of the underscore code to remove all references to underscore.js itself:
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
Please note that this code can be simplified if you don't need all the options that underscore support.
Please find below a very simple and non-configurable version of this function:
function throttle (callback, limit) {
var waiting = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!waiting) { // If we're not waiting
callback.apply(this, arguments); // Execute users function
waiting = true; // Prevent future invocations
setTimeout(function () { // After a period of time
waiting = false; // And allow future invocations
}, limit);
}
}
}
Edit 1: Removed another reference to underscore, thx to #Zettam 's comment
Edit 2: Added suggestion about lodash and possible code simplification, thx to #lolzery #wowzery 's comment
Edit 3: Due to popular requests, I added a very simple, non-configurable version of the function, adapted from #vsync 's comment
What about this?
function throttle(func, timeFrame) {
var lastTime = 0;
return function () {
var now = Date.now();
if (now - lastTime >= timeFrame) {
func();
lastTime = now;
}
};
}
Simple.
You may be interested in having a look at the source.
callback: takes the function that should be called
limit: number of times that function should be called within the time limit
time: time span to reset the limit count
functionality and usage: Suppose you have an API that allows user to call it 10 times in 1 minute
function throttling(callback, limit, time) {
/// monitor the count
var calledCount = 0;
/// refresh the `calledCount` varialbe after the `time` has been passed
setInterval(function(){ calledCount = 0 }, time);
/// creating a closure that will be called
return function(){
/// checking the limit (if limit is exceeded then do not call the passed function
if (limit > calledCount) {
/// increase the count
calledCount++;
callback(); /// call the function
}
else console.log('not calling because the limit has exceeded');
};
}
////////////////////////////////////////////////////////////
// how to use
/// creating a function to pass in the throttling function
function cb(){
console.log("called");
}
/// calling the closure function in every 100 milliseconds
setInterval(throttling(cb, 3, 1000), 100);
Adding to the discussion here (and for more recent visitors), if the reason for not using the almost de facto throttle from lodash is to have a smaller sized package or bundle, then it's possible to include only throttle in your bundle instead of the entire lodash library. For example in ES6, it would be something like:
import throttle from 'lodash/throttle';
Also, there is a throttle only package from lodash called lodash.throttle which can be used with a simple import in ES6 or require in ES5.
I've just needed a throttle/debounce function for window resize event, and being curious, I also wanted to know what these are and how they work.
I've read multiple blog posts and QAs on SO, but they all seem to overcomplicate this, suggest libraries, or just provide descriptions and not simple plain JS implementations.
I won't provide a description since it's plentiful. So here's my implementation:
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
These might need tweaks (e.g., initially the callback isn't called immediately).
See the difference in action (try resizing the window):
function throttle(callback, delay) {
var timeoutHandler = null;
return function () {
if (timeoutHandler == null) {
timeoutHandler = setTimeout(function () {
callback();
timeoutHandler = null;
}, delay);
}
}
}
function debounce(callback, delay) {
var timeoutHandler = null;
return function () {
clearTimeout(timeoutHandler);
timeoutHandler = setTimeout(function () {
callback();
}, delay);
}
}
var cellDefault = document.querySelector("#cellDefault div");
var cellThrottle = document.querySelector("#cellThrottle div");
var cellDebounce = document.querySelector("#cellDebounce div");
window.addEventListener("resize", function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDefault.appendChild(span);
cellDefault.scrollTop = cellDefault.scrollHeight;
});
window.addEventListener("resize", throttle(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellThrottle.appendChild(span);
cellThrottle.scrollTop = cellThrottle.scrollHeight;
}, 500));
window.addEventListener("resize", debounce(function () {
var span = document.createElement("span");
span.innerText = window.innerWidth;
cellDebounce.appendChild(span);
cellDebounce.scrollTop = cellDebounce.scrollHeight;
}, 500));
table {
border-collapse: collapse;
margin: 10px;
}
table td {
border: 1px solid silver;
padding: 5px;
}
table tr:last-child td div {
width: 60px;
height: 200px;
overflow: auto;
}
table tr:last-child td span {
display: block;
}
<table>
<tr>
<td>default</td>
<td>throttle</td>
<td>debounce</td>
</tr>
<tr>
<td id="cellDefault">
<div></div>
</td>
<td id="cellThrottle">
<div></div>
</td>
<td id="cellDebounce">
<div></div>
</td>
</tr>
</table>
JSFiddle
Here's how I implemented throttle function in ES6 in 9LOC, hope it helps
function throttle(func, delay) {
let timeout = null
return function(...args) {
if (!timeout) {
timeout = setTimeout(() => {
func.call(this, ...args)
timeout = null
}, delay)
}
}
}
Click on this link to see how it works.
I've seen a lot of answers here that are way too complex for "a simple throttle in js".
Almost all of the simpler answers just ignore calls made "in throttle" instead of delaying execution to the next interval.
Here's a simple implementation that also handles calls "in throttle":
const throttle = (func, limit) => {
let lastFunc;
let lastRan = Date.now() - (limit + 1); //enforces a negative value on first run
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args);
lastRan = Date.now();
}, limit - (Date.now() - lastRan)); //negative values execute immediately
}
}
This is almost the exact same implementation for a simple debounce. It just adds a calculation for the timeout delay which requires tracking when the function was last ran. See below:
const debounce = (func, limit) => {
let lastFunc;
return function(...args) {
const context = this;
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func.apply(context, args)
}, limit); //no calc here, just use limit
}
}
Simple solution in ES6. Codepen Demo
const handleOnClick = () => {
console.log("hello")
}
const throttle = (func, delay) => {
let timeout = null;
return function (...args) {
if (timeout === null) {
func.apply(this, args);
timeout = setTimeout(() => {
timeout = null;
}, delay)
}
}
}
document.querySelector("#button").addEventListener("click", throttle(handleOnClick, 500))
<button type="button" id="button">Click me</button>
Here's my own version of Vikas post:
throttle: function (callback, limit, time) {
var calledCount = 0;
var timeout = null;
return function () {
if (limit > calledCount) {
calledCount++;
callback();
}
if (!timeout) {
timeout = setTimeout(function () {
calledCount = 0
timeout = null;
}, time);
}
};
}
I find that using setInterval is not a good idea.
With leading and trailing invocations:
const throttle = (fn, ms) => {
let locked = false
return function () {
if (!locked) {
locked = true
fn.apply(this, arguments)
setTimeout(() => {
fn.apply(this, arguments)
locked = false
}, ms)
}
}
}
Test case:
function log({ gender, address }) {
console.log({
name: this.name,
gender,
address,
})
}
const jack = {
name: 'Jack',
log: throttle(log, 3000),
}
Array.from({ length: 5 }, () => jack.log({ gender: 'Male', address: 'LA' }))
I made a npm package with some throttling functions:
npm install function-throttler
throttleAndQueue
Returns a version of your function that can be called at most every W milliseconds, where W is wait. Calls to your func that happen more often than W get queued up to be called every W ms
throttledUpdate
Returns a version of your function that can be called at most every W milliseconds, where W is wait. for calls that happen more often than W the last call will be the one called (last takes precedence)
throttle
limits your function to be called at most every W milliseconds, where W is wait. Calls over W get dropped
There is a library suited for this purpose, it's Backburner.js from Ember.
https://github.com/BackburnerJS/
You'd use it so.
var backburner = new Backburner(["task"]); //You need a name for your tasks
function saySomething(words) {
backburner.throttle("task", console.log.bind(console, words)
}, 1000);
}
function mainTask() {
"This will be said with a throttle of 1 second per word!".split(' ').map(saySomething);
}
backburner.run(mainTask)
This throttle function is build on ES6. Callback functions takes arguments (args), and still it works wrapped with throttle function. Be free to customize delay time according to your app needs. 1 time per 100ms is used for development mode, event "oninput" is just an example for frequent case of its use:
const callback = (...args) => {
console.count('callback throttled with arguments:', args);
};
throttle = (callback, limit) => {
let timeoutHandler = 'null'
return (...args) => {
if (timeoutHandler === 'null') {
timeoutHandler = setTimeout(() => {
callback(...args)
timeoutHandler = 'null'
}, limit)
}
}
}
window.addEventListener('oninput', throttle(callback, 100));
P.S. As #Anshul explained: throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds."
In below example, try clicking the button multiple times, but the myFunc function would be executed only once in 3 sec.
The function throttle is passed with the function to be executed and the delay.It returns a closure, which is stored in obj.throttleFunc.
Now since obj.throttleFunc stores a closure, the value of isRunning is maintained inside it.
function throttle(func, delay) {
let isRunning;
return function(...args) {
let context = this; // store the context of the object that owns this function
if(!isRunning) {
isRunning = true;
func.apply(context,args) // execute the function with the context of the object that owns it
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc(param) {
console.log(`Called ${this.name} at ${param}th second`);
}
let obj = {
name: "THROTTLED FUNCTION ",
throttleFunc: throttle(myFunc, 3000)
}
function handleClick() {
obj.throttleFunc(new Date().getSeconds());
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
If we don't want the context or arguments to be passed, then a simpler
version of this would be as following:
function throttle(func, delay) {
let isRunning;
return function() {
if(!isRunning) {
isRunning = true;
func()
setTimeout(function() {
isRunning = false;
}, delay);
}
}
}
function myFunc() {
console.log('Called');
}
let throttleFunc = throttle(myFunc, 3000);
function handleClick() {
throttleFunc();
}
button {
width: 100px;
height: 50px;
font-size: 20px;
}
<button onclick="handleClick()">Click me</button>
I also want to suggest a simple solution for when there is only 1 function you know you will call (for example: Search)
here is what i did in my project
let throttle;
function search() {
if (throttle) {
clearTimeout(throttle);
}
throttle = setTimeout(() => {
sendSearchReq(str)
}, 500);
}
Search is called on input change event
function throttle(targetFunc, delay){
let lastFunc;
let lastTime;
return function(){
const _this = this;
const args = arguments;
if(!lastTime){
targetFunc.apply(_this, args);
lastTime = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function(){
targetFunc.apply(_this, args);
lastTime = Date.now();
}, delay - (Date.now() - lastTime));
}
}
}
Try it :
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));
CodeSandbox
const { now } = Date;
export default function throttle(func, frameDuration) {
let timeout = null;
let latest;
const epoch = now();
function getDurationToNextFrame() {
const elapsed = now() - epoch;
const durationSinceLastFrame = elapsed % frameDuration;
return frameDuration - durationSinceLastFrame;
}
function throttled(...args) {
latest = () => {
func.apply(this, args);
};
if (!timeout) {
timeout = setTimeout(() => {
latest();
timeout = null;
}, getDurationToNextFrame());
}
}
return throttled;
}
Simple throttle function -
Note- Keep on clicking on the button , You'll see console log at first on click and then only after every 5 seconds until you're keep clicking.
HTML -
<button id='myid'>Click me</button>
Javascript -
const throttle = (fn, delay) => {
let lastTime = 0;
return (...args) => {
const currentTime = new Date().getTime();
if((currentTime - lastTime) < delay) {
return;
};
lastTime = currentTime;
return fn(...args);
}
};
document.getElementById('myid').addEventListener('click', throttle((e) => {
console.log('I am clicked');
}, 5000));
We can also implement using a flag-
var expensive = function(){
console.log("expensive functionnns");
}
window.addEventListener("resize", throttle(expensive, 500))
function throttle(expensiveFun, limit){
let flag = true;
return function(){
let context = this;
let args = arguments;
if(flag){
expensiveFun.apply(context, args);
flag = false;
setTimeout(function(){
flag = true;
}, limit);
}
}
}
Here is a bit modernized and simplified version of #clément-prévost answer
function throttle(func, wait, options = {}) {
let timeout = null;
let previous = 0;
const later = (...args) => {
previous = options.leading === false ? 0 : Date.now();
func(...args);
};
return (...args) => {
const now = Date.now();
if (!previous && options.leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func(...args);
} else if (options.trailing !== false) {
clearTimeout(timeout);
timeout = setTimeout(() => later(...args), remaining);
}
};
}
function myFunc(a) {
console.log(`Log: ${a} ${this.val}`);
}
const myFuncThrottled = throttle(myFunc.bind({val: 42}), 1234, {leading: true, trailing: true})
myFuncThrottled(1)
myFuncThrottled(2)
myFuncThrottled(3)
function throttle(CB,ms=300,Id='Identifier for the callback(CB)'){
Id = Id || ""+CB
var N = throttle.N = throttle.N || {}; // Static variable N to store all callbacks ids and their status
if( N[Id] ) return; // already in the queue to run
N[Id] = 1; // add it the queue
setTimeout(()=>{
N[Id] = 0; // remove it from the queue
CB(); // finally call the function
}, ms);
}
for(var i=0;i<100;i++){
throttle(e=>console.log("Hi1"),1e3,'F1');
}
// will only output : Hi1
// this function guarantee the callback to run at least once
Some great solutions here already, but I was looking for a modern version with trailing (and optionally leading) executions, with the last passed arguments provided to each function call:
const throttle = (fn, wait=500, leading=true) => {
let prev, timeout, lastargs;
return (...args) => {
lastargs = args;
if (timeout) return;
timeout = setTimeout(() => {
timeout = null;
prev = Date.now();
// let's do this ... we'll release the stored args as we pass them through
fn.apply(this, lastargs.splice(0, lastargs.length));
// some fancy timing logic to allow leading / sub-offset waiting periods
}, leading ? prev && Math.max(0, wait - Date.now() + prev) || 0 : wait);
};
}
Usage:
x = throttle((...args) => console.log(...args));
let n = 0;
x(++n, 'boom');
x(++n, 'boom');
x(++n, 'boom');
if there will be more than one function defining them one by one would not be maintainable so i would suggest use a helper class to keep values for each
class slowDown {
constructor(cb,timeGap){
this.last = 0
this.run = function(){
let current = Date.now(),
shouldRun = (current - this.last) >= timeGap
if(shouldRun){
cb(current - this.last)
this.last = current
}
}
}
}
// example use
const press = new slowDown(timeElapsed => {
// define function here which you wanted to slow down
console.log("pressed after " + timeElapsed + " ms")
},750)
window.addEventListener("keydown",()=>{
press.run()
})
Below is the simplest throttle I could think of, in 13 LOC. It creates a timeout each time the function is called and cancels the old one. The original function is called with the proper context and arguments, as expected.
function throttle(fn, delay) {
var timeout = null;
return function throttledFn() {
window.clearTimeout(timeout);
var ctx = this;
var args = Array.prototype.slice.call(arguments);
timeout = window.setTimeout(function callThrottledFn() {
fn.apply(ctx, args);
}, delay);
}
}
// try it out!
window.addEventListener('resize', throttle(function() {
console.log('resize!!');
}, 200));

Promise returning unexpectedly

I have the following:
return indexedDbClient.getStorageUsedInGb().then(function (storageUsedInGb) {
var evictedMediaGuids = [];
storageUsedInGb = parseFloat(storageUsedInGb);
if (storageUsedInGb > storageQuotaInGb) {
return new Promise(function(resolve, reject){
const store = database.transaction(storeName, "readwrite").objectStore(storeName);
(function loop(storageUsedInGb) {
if (storageUsedInGb <= storageQuotaInGb) {
resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
} else {
const latestMediaRequest = store.getAll();
latestMediaRequest.onsuccess = function (event) {
const allData = event.target.result;
const targetEntry = allData[0];
const deleteRequest = store.delete(targetEntry.media.guid);
evictedMediaGuids.push(targetEntry.media.guid);
deleteRequest.onsuccess = loop.bind(null, storageUsedInGb - event.target.media.size / 1024 / 1000 / 1000);
deleteRequest.onerror = reject;
}
latestMediaRequest.onerror = reject;
}
})(storageUsedInGb); // call immediately
})
} else {
return Promise.resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
}
}).then(function (storeObject) {
// do stuff to object
return Promise.resolve(storeObject)
});
The idea is that loop(storageUsedInGb) forces the resolution to wait for the return; however handleStoreObject gets invoked immediately after loop - with no sign of the latestMediaRequest onsuccess handler being invoked. What am I doing wrong?
I am using bluebird in case it matters.

setTimeout not working in script for retrying database connections

I'm working on some code where I want to try to make a database connection and retry a limited number times, increasing the time between each attempt by double.
I have a semi-working version. The only issue is that I don't believe the setTimeout timing is working as each consecutive retry appears to execute immediately. Here's the script on jsfiddle.
var NUM_RETRIES = 10;
var TIMEOUT = 1;
var initConnection = function(callback) {
var result = Math.floor((Math.random()*5)+1);
console.log(result);
if(result > 1) {
return callback('error: trouble connecting.');
}
return callback(null);
};
var tryConnection = function(callback) {
setTimeout(initConnection(function(err) {
if(err) {
TIMEOUT *= 2000;
NUM_RETRIES -= 1;
if(NUM_RETRIES) {
return tryConnection.call(this, callback);
}
return callback('error: gave up.');
}
return callback();
}), 20000);
};
var run = function() {
tryConnection(function(err) {
if(err) {
return console.log(err);
}
console.log('connected!');
});
};
run();
Try this:
// initConnection should be called a max of 50 times
// timeout timer in ms
var NUM_RETRIES = 50;
var TIMEOUT = 200;
var initConnection = function (callback) {
var result = Math.floor((Math.random() * 5) + 1);
console.log(result);
if (result > 1) {
return callback('error: trouble connecting.');
}
return callback(null);
};
var tryConnection = function (callback) {
setTimeout(function () {
console.log('timeout:' + TIMEOUT + 'ms');
initConnection(function (err) {
if (err) {
TIMEOUT = TIMEOUT * 2;
NUM_RETRIES -= 1;
if (NUM_RETRIES) {
return tryConnection.call(this, callback);
}
return callback('error: gave up.');
}
return callback();
})
}, TIMEOUT);
};
var run = function () {
tryConnection(function (err) {
if (err) {
return console.log(err);
}
console.log('connected!');
});
};
run();
In setTimeout, we need to pass a function which will call after the time delay. But you are calling that function/method in timeout, instead of passing it, which results in unexpected behaviour.
Example:
var callMe = functiion() {}
setTimeout(callMe(), 20000); // call immediately
setTimeout(callMe, 20000); // call after 20000ms delay
JSFiddle Demo

Categories