Acces to object scope in a function with multiple params in JS - javascript

I got this function:
const debounce = (func, delay) => {
let inDebounce;
return function() {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => func.apply(context, args), delay)
}
}
var likes_btn = document.getElementsByClassName("js-submit-like");
for (var i = 0; i < likes_btn.length; i++) {
likes_btn[i].addEventListener('click', debounce(function(el) {
alert("hello");
el.preventDefault();
}, 500));
}
So the thing is, that I need to use the .preventDefault() before the debounce gets executed. Currently, what really happens is that is executed at the end of debounce(), not into the function scope.
How can I acces into the function scope? Thanks!

Just move it outside of the debouncer callback:
const debouncer = debounce(() => alert("hello"), 500);
likes_btn[i].addEventListener('click', function(event) {
event.preventDefault();
debouncer(event);
});
That might be a bit more elegant with some chained functions:
const both = (fn1, fn2) => (...args) => (fn1(...args), fn2(...args));
const stop = event => event.preventDefault();
const listen = (el, name, handler) => el.addEventListener(name, handler);
for(const btn of likes_btn) {
listen(btn, "click", both(
debounce(() => alert("hello"), 500),
stop
));
}

Create a separate debouncer callback that the listener can close over:
const debounce = (func, delay) => {
let inDebounce;
return function() {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => func.apply(context, args), delay)
}
}
var likes_btn = document.getElementsByClassName("js-submit-like");
for (let i = 0; i < likes_btn.length; i++) {
const button = likes_btn[i];
const debouncer = debounce((e) => console.log("Hello", button), 500); // you can also use e.target to refer to the button
button.addEventListener('click', function(e) {
console.log("clicked");
debouncer(e);
e.preventDefault();
});
}
<a class="js-submit-like" href="http://www.facebook.com/">Like</a>
<a class="js-submit-like" href="http://www.facebook.com/">Like 2</a>

Related

Getting a debounced function to return a variable

I have a function called debouncedGetScrolledDownPercentage that I want to return the scrollPercentage. As it is debounced, it incorrectly returns undefined, presumably because the debounce function does not return anything.
How can debouncedGetScrolledDownPercentage() return the scrollPercentage, so that scrolledDownPercentage: debouncedGetScrolledDownPercentage() is not undefined? I assume a global variable accessible to all scopes could solve it, but I would prefer a different solution.
Any help would be appreciated, thank you.
For context, here is all the code:
window.onbeforeunload = function() { return "Are you sure you want to leave?"; }; // for testing, so you can click close and the browser won't close and you can still see the logs
// initial data
let bounced = true;
const sessionStartTime = new Date();
let maxScrollPosition = 0;
let totalScrollDistance = 0;
// functions
const setBouncedToFalse = () => {
bounced = false;
document.removeEventListener("click", setBouncedToFalse);
};
const calculateSessionTime = () => {
const sessionEndTime = new Date();
return sessionEndTime - sessionStartTime; // sessionTimeMs
}
const debounce = (func, delay) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};
const debouncedGetScrolledDownPercentage = debounce(() => {
// const debouncedGetScrolledDownPercentage = debounce((callback) => {
const scrollPosition = window.scrollY;
let scrollPercentage = 0;
if (scrollPosition > maxScrollPosition) {
const scrollDistance = scrollPosition - maxScrollPosition;
totalScrollDistance += scrollDistance;
maxScrollPosition = scrollPosition;
const maxPossibleScrollDistance = document.documentElement.scrollHeight - window.innerHeight;
scrollPercentage = Math.round(totalScrollDistance / maxPossibleScrollDistance * 100);
}
return scrollPercentage;
// callback(scrollPercentage);
}, 200);
const processData = () => {
const userData = {
sessionTimeMs: calculateSessionTime(),
bounced,
scrolledDownPercentage: debouncedGetScrolledDownPercentage() // incorrectly returns undefined
};
debouncedGetScrolledDownPercentage((scrollPercentage) => {
userData.scrolledDownPercentage = scrollPercentage;
console.log('userData: ', userData);
});
}
// events
document.addEventListener("click", setBouncedToFalse); // track clicks (if user bounces)
window.addEventListener('scroll', debouncedGetScrolledDownPercentage); // needed?
window.addEventListener("beforeunload", processData); // process data before user leaves

JavaScript Throttle always returning function

I'm trying to understand JavaScript Throttling. I implemented a very basic throttle for the knowledge that I have so far.
const display = (msg) => {
return msg;
}
const throttleDisplay = (func, limit) => {
let flag = true;
return function() {
if(flag) {
func.apply(this, arguments);
flag = false;
setTimeout(() => flag = true, limit);
}
}
}
for(let i=1; i<=5; i++) {
setTimeout(() => {
const result = throttleDisplay(display("Hi"), 6000);
console.log(result)
}, i*1000);
}
My console.log is returning [Function] instead of the message Hi. Also, it is returning [Function] 5 times. Shouldn't it ignore the next call until the limit, 6000ms, has passed?
Thanks a lot!
Throttling works differently.
First, you should name your throttle function just throttle, as it is not specifically linked with display. Then, you should call this throttle to get a function that is specific to display. And only then you would use that secondary function in the actual use case you have for it, i.e. with the timer.
Here is your code corrected:
const throttle = (func, limit) => {
let flag = true;
return function() {
if(flag) {
func.apply(this, arguments);
flag = false;
setTimeout(() => flag = true, limit);
}
}
};
const throttleDisplay = throttle(() => console.log("Hi"), 6000);
for(let i=1; i<=10; i++) {
setTimeout(throttleDisplay, i*1000);
}

Promise await blocking loop from finishing

I need a loop to play 10 sounds in sequence. My first attempt had the sounds overlapping, so I was told that I need to use Promise/await. The code below plays sound 0 then never continues the loop.
(The library I'm using (jscw) is for morse code. You pass it a string, it plays the morse equivalent. Its "onFinished" calls a user-defined function.)
async function playAll() {
for (let i = 0; i < 10; i++) {
playMorse(words[i]);
await playstate();
}
}
function playstate() {
playdone = true;
//console.log(playdone);
return new Promise((resolve) => {
window.addEventListener('playdone', resolve)
})
}
function playMorse(z) {
var m = new jscw();
playdone = false;
m.onFinished = function() {
playstate();
}
m.play(z);
}
It seems nothing is supposed to fire the playdone event you are listening for.
So a simple solution is to fire it in the onFinished callback.
const words = ["hello", "world"];
async function playAll() {
for (let i = 0; i < 2; i++) {
console.log("###READING", words[i]);
playMorse(words[i]);
await playstate();
}
}
function playstate() {
playdone = true;
//console.log(playdone);
return new Promise((resolve) => {
window.addEventListener('playdone', resolve, { once: true })
})
}
function playMorse(z) {
var m = new jscw();
playdone = false;
m.onFinished = function() {
dispatchEvent( new Event("playdone") );
}
m.play(z);
}
btn.onclick = playAll;
<script src="https://fkurz.net/ham/jscwlib/src/jscwlib.js"></script>
<button id="btn">play all</button>
But you don't need an event here, simply make playMorse return a Promise that will resolve in the onFinished callback:
const words = ["hello", "world"];
async function playAll() {
for (let i = 0; i < 2; i++) {
console.log("###READING", words[i]);
await playMorse(words[i]);
}
}
function playMorse(z) {
return new Promise( (resolve) => {
const m = new jscw();
m.onFinished = resolve;
m.play(z);
});
}
btn.onclick = playAll;
<script src="https://fkurz.net/ham/jscwlib/src/jscwlib.js"></script>
<button id="btn">play all</button>

removeEventListener using the EventTarget

I have an array with the button objects.
When it is clicked, it gets the "button-active" tag and
when it is clicked again, it removes the "button-active" class
I want to removeEventListener when flag is true
let flag = false;
const buttonActive = () => {
arr.forEach(e => {
e.addEventListener("click", function eventListener(event){
event.preventDefault()
if(checkClass(e, "button-active")) removeClass(e, "button-active")
else addClass(e, "button-active")
})
})
}
button.addEventListener("click", (event) => {
event.preventDefault()
let input = document.createElement('div')
input.className = "info"
input.innerHTML += `...(some html with buttons that have weekday class)...`
info.appendChild(input)
flag = true;
arr = document.querySelectorAll('.weekday')
buttonActive()
})
I thought of a way of putting the eventListener function outside the buttonActive function, but the eventListener function uses the variable e.
How should I solve this problem?
simple~
let flag = false;
let cbs = [];
const buttonActive = (arr, active) => {
if (active) {
cbs = arr.map(e => {
const cb = (event) => {
event.preventDefault()
if(checkClass(e, "button-active")) removeClass(e, "button-active")
else addClass(e, "button-active")
}
e.addEventListener("click", cb)
return cb;
});
} else {
for (int i = 0; i < arr.length; ++i) {
arr[i].removeEventListener('click', cbs[i]);
}
}
}
// I guess this is the trigger button
button.addEventListener("click", (event) => {
event.preventDefault()
let input = document.createElement('div')
input.className = "info"
input.innerHTML += `...(some html with buttons that have weekday class)...`
info.appendChild(input)
flag = !!flag;
arr = document.querySelectorAll('.weekday')
buttonActive(arr, flag)
})

How to return result of the nested function in javascript arrow function?

Can't figure out how to return result of the nested function in arrow function.
How to express this one (works fine):
var stopEating = (function() {
var loadedStomach = false;
return function() {
if(!loadedStomach){
loadedStomach = true;
console.log('Stop eating');
}};
})();
as an arrow function (doesn't work properly):
const stopEating = () => {
let loadedStomach = false;
return () => {
if(!loadedStomach) {
loadedStomach = true;
console.log('Its enough eating!');
}};
};
You need to call the function in order to get the results, thus, adding the parentheses at the end.
const stopEating = (() => {
let loadedStomach = false;
return () => {
if(!loadedStomach) {
loadedStomach = true;
console.log('Its enough eating!');
}
};
})();
Into the first example, you created Immediately Invoked Function Expression (IIFE).
It's a JavaScript function that runs as soon as it is defined. That's why you receive internal function, which prints "Stop Eating".
To realize this pattern you just need to wrap arrow function:
const stopEating = (() => {
let loadedStomach = false;
return () => {
if(!loadedStomach) {
loadedStomach = true;
console.log('Its enough eating!');
}
};
})();

Categories