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);
}
Related
I'm trying to show the process of how the backtracking algorithm solves a sudoku board, but I'm not sure how I can make sure that it only gets called every 500ms.
function solveBoard(board) {
// I tried doing setTimeout from here to the bottom, but it breaks the solver and just puts 9s everywhere.
let empty = findEmpty(board);
if (!empty) return true;
let row = empty[0];
let col = empty[1];
for (let i = 1; i < 10; i++) {
board[row][col] = i;
console.log(board[row][col]);
document.getElementById(`${row}-${col}`).value = i;
if (checkValid(board, row, col)) {
if (solveBoard(board)) {
return true;
}
}
board[row][col] = 0;
}
return false;
}
The first time I call solve board is just an event listener.
solveBtn.addEventListener("click", () => {
solveBoard(boardArray);
});
Call sleep in solveBoard
async function solveBoard(board) {
await sleep()
// …
}
function sleep(ms = 500) {
return new Promise(resolve => setTimeout(resolve, ms))
}
// Demo
(async () => {
console.log('a')
await sleep()
console.log('b')
await sleep()
console.log('c')
})();
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>
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!');
}
};
})();
I have an async function that has a loop that I need to be able to pause or unpause it. This is what I have so far.
I use a flag to pause the flow:
let flag = true;
function flag_func() {
flag = !flag;
}
$(document).ready(function () {
function sleep(ms) {
while (!flag) {
//...waiting.. but infinite loop
}
return new Promise(resolve => setTimeout(resolve, ms));
}
async function show_simulation(data) {
document.getElementById("solve-button").outerHTML = "<button type=\"button\" id='pause-button' onclick='flag_func()' class=\"btn btn-primary btn-lg\">Pause</button>";
//simulation
if (data.length === 0) {
console.log('stuff')
} else {
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
}
}
});
The problem is that is being paused, but due the while block the flow, I can't unpause the for loop.
Any idea about how I can solve this?
It might be a nice use case for async iterables. It involves a bit of boilerplate to create your async list, but then the code is much nicer. Basically you would have:
import AsyncList from './async-list.js'
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function f(data) {
const list = new AsyncList(data);
document.getElementById("btn-toggle").addEventListener("click", function () {
if (list.paused) {
this.textContent = "Pause";
list.resume();
} else {
this.textContent = "Resume";
list.pause()
}
})
for await (let item of list) {
console.log(item)
await sleep(1000);
}
console.log("end of loop")
}
f([10, "hello", 1029, 90, 80, 209, 44])
A possible implementation of AsyncList could be:
export default class AsyncList {
constructor(array) {
// shallow copy
this._array = array.slice();
this._index = 0;
this._length = this._array.length;
this.paused = false;
this._resume = () => {}; // noop, in case `resume` is called before `pause`
}
[Symbol.asyncIterator]() {
return this;
}
pause() {
this.paused = true;
}
resume() {
this.paused = false;
this._resume();
}
next() {
if (this._index < this._length) {
const value = this._array[this._index++];
if (this.paused) {
return new Promise(r => this._resume = r.bind(null, { value }))
}
return Promise.resolve({ value })
} else {
return Promise.resolve({ done: true });
}
}
}
Just to give to you the idea, you could also encapsulate the private properties, and check more scenarios (here I assume data is an array, for example, not just an iterable).
I'd replace:
let i;
for (i = 0; i < data.length; i++) {
await sleep(40);
// do stuff
}
...with...
let i = 0;
const doStuff = () => {
// do stuff
if (++i < data.length) {
setTimeout(doStuff, 40);
}
};
setTimeout(doStuff, 40);
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>