Let's say there is a code in place 2
var place2IsReady = true;
In place 1 I need to implement the logic below :
Once place2IsReady value was changed (to true) then display alert('ready!');
Notes:
place2IsReady variable is not available in the scope of place 1.
the code from place 1 gets executed before place 2 gets executed (or there is a race condition).
Solution 1
I believe I can use window.place2IsReady instead and use setTimeout/setInterval in place 1 until I get window.place2IsReady === true.
Any better options? Using Listeners? On the variable change?
P.S. I need to track only first possible change of place2IsReady.
Is there a better way? Thank you.
You can create a listener for the variable change using setTimeout, something like:
let place2IsReady = false;
setReadyListener();
// testing wait 2 seconds to set place2IsReady to true
// so: an alert should occur after 2 seconds
setTimeout(() => place2IsReady = true, 2000);
function setReadyListener() {
const readyListener = () => {
if (place2IsReady) {
return alert("Ready!");
}
return setTimeout(readyListener, 250);
};
readyListener();
}
A more generic listener 'factory' could be:
let place2IsReady = false;
let fromObj = {
place2IsReady: "busy",
done() { this.place2IsReady = "done"; },
};
const listen = changeListenerFactory();
listen(
() => place2IsReady,
() => console.log("place2IsReady") );
listen(
() => fromObj.place2IsReady === "done",
() => console.log("formObj.place2IsReady done!") );
console.log("Listening...");
// test change variables with listeners
setTimeout(() => place2IsReady = true, 1000);
setTimeout(() => fromObj.done(), 3000);
function changeListenerFactory() {
const readyListener = (condition, callback, delay) => {
if (!condition || typeof condition !== "function") { return true; }
if (condition()) {
return callback();
}
setTimeout(() => readyListener(condition, callback, delay), delay);
};
return (condition, callback = () => {}, delay = 250) =>
readyListener(condition, callback, delay);
}
Or maybe using a Proxy (with a set trap) works for you
const readyState = new Proxy({ ready: false }, {
set (target, prop, val) {
console.log(`readyState.ready changed from ${target[prop]} to ${val}`);
target[prop] = val;
}
});
console.log("Waiting for changes ...");
setTimeout(() => readyState.ready = true, 2000);
Assuming you can replace place2IsReady with an object:
place2IsReady = {
state: false,
set ready(value) {
this.state = value
state && place_1_call()
},
get ready() {
return state
}
}
place_1_call = () => {
alert('ready')
}
place2IsReady.ready = true
Related
this is probably a simple mistake. I'm trying to implement a throttle function (Credit to our man webdevsimplified for quality content: https://blog.webdevsimplified.com/2022-03/debounce-vs-throttle/).
My throttle implementation is not working and I am not sure why. How can I get the throttle working?
function printHi() {
console.log("Hi");
}
function throttle(cb, delay = 1000) {
let shouldWait = false
let waitingArgs
const timeoutFunc = () => {
if (waitingArgs == null) {
shouldWait = false
} else {
cb(...waitingArgs)
waitingArgs = null
setTimeout(timeoutFunc, delay)
}
}
return (...args) => {
console.log("should wait?", shouldWait);
if (shouldWait) {
// It never goes in here...?
waitingArgs = args
return
}
cb(...args)
shouldWait = true
setTimeout(timeoutFunc, delay)
}
}
<button onclick="(function () {
throttle(printHi)();
})()">Click me</button>
Consecutive button clicks print to the console:
should wait? false
Hi
should wait? false
Hi
should wait? false
Hi
shouldWait is never printed true, even though it should...
Your original implementation didn't work because shouldWait and waitingArgs were scoped to the function, so every function run had a fresh set of these variables with shouldWait = false.
You might have made this mistake due to the scope differences between var and let, the former was globally scoped if used this way.
Here is my solution, which simply moved the 2 variables out of the function.
function printHi() {
console.log("Hi");
}
let shouldWait = false
let waitingArgs
function throttle(cb, delay = 1000) {
const timeoutFunc = () => {
if (waitingArgs == null) {
shouldWait = false
} else {
cb(...waitingArgs)
waitingArgs = null
setTimeout(timeoutFunc, delay)
}
}
return (...args) => {
console.log("should wait?", shouldWait);
if (shouldWait) {
// It never goes in here...?
waitingArgs = args
return
}
cb(...args)
shouldWait = true
setTimeout(timeoutFunc, delay)
}
}
<body>
<button onclick="(function () {
throttle(printHi)();
})()">Click me</button>
</body>
My class:
var indexedDBInitInterval; // clearInterval Doesnt work when done from within class for some reason..
var coumter = 1;
class IndexedDBWrapper {
constructor() {
this._db = undefined
this._dbInitInterval = undefined
this.dbInitRequest = indexedDB.open("someDB", 1)
this.dbInitRequest.onerror = (event) => { this.dbInitRequestOnError(event) }
this.dbInitRequest.onsuccess = (event) => { this.dbInitRequestOnSuccess(event) }
this.dbInitRequest.onupgradeneeded = (event) => { this.dbInitRequestOnUpgradedeNeeded(event) }
}
isDBInitalized() {
return new Promise((resolve) => {
indexedDBInitInterval = setInterval(() => {
log(this.dbInitRequest)
log(this.dbInitRequest.readyState)
log(indexedDBInitInterval)
coumter = coumter + 1
if (this.dbInitRequest.readyState == "done") {
log("mutants")
log(coumter)
log(clearInterval(indexedDBInitInterval))
resolve()
}
}, 300)
})
}
dbInitRequestOnError(event) {
log("Error initializing IndexedDB: ")
log(event)
}
And calling with:
indexedDBWrapper.isDBInitalized().then(() => {
Neither clearInterval or resolve gets fired, even tho log("mutants") gets fired.
What a puzzle..
You'd want to make indexedDBInitInterval a variable within isDBInitialized. Otherwise if you call the function multiple (times or even on multiple objects), they would interfere with each other. The same can be said for coumter, although that might just be a debug variable.
Does indexedDBWrapper.isDBInitalized().then(() => console.log('OK')) print OK? I can understan the clearInterval failing if the wrong indexedDBInitInterval is used, but mutants being logged should indicate that resolve() also gets called, unless an error happens in between.
I want most understandable syntax for polling a flag and return when it is true, my code snippet below doesn't work I know, what's the syntax that would make it work if you get my idea ?
async function watch(flag) {
let id = setInterval(function(){
if (flag === true) {
clearInterval(id);
}
}, 1000);
return flag;
}
If you want to poll a variable where the value is a primative, then you need to define it outside the function, otherwise it can't change.
If you want to have a promise resolve when that condition is done, then you have to create it explicitly. async and await are tools for managing existing promises.
let flag = false;
function watchFlag() {
return new Promise(resolve => {
let i = setInterval(() => {
console.log("Polling…");
if (flag) {
resolve();
clearInterval(i);
}
}, 500);
});
}
setTimeout(() => {
flag = true;
}, 1500);
console.log("Watching the flag");
watchFlag().then(() => {
console.log("The flag has changed");
});
If you don't know when the flag is going to change (in 10 seconds or in 10 minutes), you can use a setter instead. Probably an anti-pattern, but again your question doesn't really show us how you would be using this flag in your code.
const flagsObject = {
set flag(stat) {
this._flag = stat;
if (stat) {
// call the function you need to call when flag is true
// you could add additional condition if you only want to run the function
// when the flag is switched
doSomething()
}
},
get flag() {
return this._flag;
}
};
flagsObject.flag = true; // doSomething() will be called
This question already has an answer here:
Debounce function that fires FIRST then debounces subsequent actions
(1 answer)
Closed 2 years ago.
I am trying to construct a debounce function definition to give following results. My following code prints the following.
'hi'
'hi'
'hi'
'hi'
I am struggling to figure out how I can leverage interval to contain x as undefined and once this expires again the function returns 'hi'
function debounce(callback, interval) {
return () =>{
let x = callback();
setTimeout(()=>{
x = callback();
},interval);
return x;
}
}
// Expectation running below statements
function giveHi() { return 'hi'; }
const giveHiSometimes = debounce(giveHi, 3000);
console.log(giveHiSometimes()); // -> 'hi'
setTimeout(function() { console.log(giveHiSometimes()); }, 2000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 4000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 8000); // -> 'hi'
Make a calledRecently flag, which gets set to true when the timeout callback runs. When the timeout callback runs, set another timeout which runs after the interval is up to reset calledRecently to false:
function debounce(callback, interval) {
let calledRecently = false;
let intervalId;
return () => {
const result = calledRecently ? undefined : callback();
calledRecently = true;
clearInterval(intervalId);
intervalId = setTimeout(() => {
calledRecently = false;
}, interval);
return result;
}
}
const giveHiSometimes = debounce(() => 'hi', 3000);
console.log(giveHiSometimes()); // -> 'hi'
setTimeout(function() { console.log(giveHiSometimes()); }, 2000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 4000); // -> undefined
setTimeout(function() { console.log(giveHiSometimes()); }, 8000); // -> 'hi'
It sounds like you're after leading edge (immediate) debouncing where an initial event is fired, but batched events after it are debounced.
There are a few ways of writing this, including setTimeout and Date.now but it all comes down to moving a lastAttempt variable of some sort outside of the closure and not re-triggering the callback unless the cooldown time has passed without any attempts to call the function being made. Writing it without setInterval seems cleaner to me.
I'd also add an ...args parameter to the function returned from your leadingDebounce function. This lets you pass parameters into the function if you want.
const leadingDebounce = (fn, cooldown) => {
let lastAttempt = 0;
return (...args) => {
if (Date.now() - lastAttempt > cooldown) {
fn(...args);
}
lastAttempt = Date.now();
};
};
const debounced = leadingDebounce(() => console.log("hi"), 3000);
debounced(); // -> 'hi'
setTimeout(() => console.log(debounced()), 2000); // -> undefined
setTimeout(() => console.log(debounced()), 4000); // -> undefined
setTimeout(debounced, 8000); // -> 'hi'
I'm trying to create a custom debounce function:
const debounced = [];
const cancelFunc = timeout => () => {
clearTimeout(timeout);
};
function debounce(fn, wait, ...args) {
let d = debounced.find(({ func }) => func === fn);
if (d) {
d.cancel();
} else {
d = {};
debounced.push(d);
}
d.func = fn;
d.timeout = setTimeout(fn, wait, ...args);
d.cancel = cancelFunc(d.timeout);
}
If I use with a named function, it works as intended:
debounce(foo, 1000); // called once with 5 clicks in 1 second
But I can't get it to work with anonymous functions:
debounce(() => { foo(5); }, 1000); // called 5 times with 5 clicks in 1 second
I created a pen here: https://codepen.io/anon/pen/gQvMdR?editors=1011
This happens because of your find condition. Let's back up, and consider this bit of code:
if (
(function(){ return 1 }) === (function(){ return 1 })
) {
console.log('The functions are equal');
} else {
console.log('The functions are NOT equal');
}
// logs 'The functions are NOT equal'
Even though I wrote two identical anonymous functions, they are not strictly equal to each other. When you pass in that anonymous function, that is essentially what you are doing. So, when you search for your array for a previously found function, it will never find a match, because each time debounce(() => { foo(5); }, 1000); is called it creates a new function. Since it'll never find a match, it will never be canceled.
As mentioned by #SLaks "Each call creates a separate function, so you won't find it in the array."
So you just need to store something in the array to match it to, you can use .toString()
// ================
const debounced = [];
const cancelFunc = timeout => () => {
clearTimeout(timeout);
};
function debounce(fn, wait, ...args) {
let d = debounced.find(({ funcString }) => funcString === fn.toString());
if (d) {
d.cancel();
} else {
d = {};
debounced.push(d);
}
d.func = fn;
d.funcString = fn.toString()
d.timeout = setTimeout(fn, wait, ...args);
d.cancel = cancelFunc(d.timeout);
}
// ================
function foo(value) {
console.log('value:', value)
}
function onClickBroken() {
debounce(() => { foo(5); }, 1000);
}
<button onClick="onClickBroken()">Click me 5 times</button>