setTimeout() being called immediately in throttle function - javascript

so I'm trying to write a throttle function that when called, calls a callback but only up to a certain limit number of times in a given interval. If the limit is reached, the callback gets pushed into a queue where the callback gets called after the initial interval.
const throttle = (cb, interval, maxCalls) => {
let calls = 0;
const records = [];
let totalCalls = 0;
return (...rest) => {
if(calls < maxCalls) {
calls++;
records.push(Date.now());
totalCalls++;
cb.apply(null, rest);
setTimeout(() => {
calls--;
}, interval);
} else {
//cb within setTimeout being invoked immediately here
setTimeout(() => {
calls++;
records.push(Date.now());
totalCalls++;
cb.apply(null, rest);
//console.log(allotedTime: interval - (Date.now() - records[(totalCalls-1)-maxCalls]));
}, interval - (Date.now() - records[(totalCalls-1)-maxCalls]));
}
}
}
const meow = (start, ...args) => {
console.log(Date.now() - start, ...args);
}
const burp = throttle(meow.bind(this, Date.now()), 10000, 2);
setTimeout(() => burp('burp'), 0); // expect 2-7 'burp'
setTimeout(() => burp('burp'), 5000); // expect 5000 'burp'
setTimeout(() => burp('burp'), 6000); // expect 10000 'burp'
setTimeout(() => burp('burp'), 7000); // expect 15000 'burp'
The main problem is that for some reason, within the else block, the function isn't waiting for the setTimeout and is being called immediately. The syntax seems fine so I'm having a hard time figuring out why it's being called. This is the output after being called:
setTimeout(() => burp('burp'), 0); //6 'burp'
setTimeout(() => burp('burp'), 5000); //5001 'burp'
setTimeout(() => burp('burp'), 6000) //6001 'burp'
//allotedTime: 4005
setTimeout(() => burp('burp'), 7000); //10008 'burp'
//allotedTime: 4993
You'll notice that if you add the allotedTime with the result from the line above, you'll get the desired log. Thanks for taking a look.
Link to repl

Not sure if I understood the problem entirely, but based on your expectations,
the else interval should be:
interval - (Date.now() - records[totalCalls-maxCalls]))
because totalCalls is increment once the callback is called. So, either add totalCalls++; as the first statement of the else block (before setInterval()) or don't expect the value to be incremented (suggestion number 1).

Related

JavaScript execute event each every time

I have this:
let time = store.time;
const run = setInterval(() => {
const delay = store.delay;
setTimeout(() => {
console.log("Execute event....");
}, delay * 1000);
time--;
if (time === 0) {
clearInterval(run);
}
}, 1000);
I just want to run a process indicating a time and delaying the time at the same time, for example:
time=10 ; delay=2, then the process will be executed when the time is equal to: 2, 4, 6, 8 and 10
time=10 ; delay=3, then the process will be executed when the time is equal to: 3, 6 and 9
time=10 ; delay=4, then the process will be executed when the time is equal to: 4 and 8
time=10 ; delay=5, then the process will be executed when the time is equal to: 5 and 10
It's not currently working that way, it's only running 10 times, why??
If you use a promise and async/await you can use a normal for loop to log the steps at the right intervals.
// Return a promise that resolves after a
// certain duration
function delay(step) {
return new Promise(res => {
setTimeout(() => res(), step * 1000);
});
}
// Accepts a limit, and a step count
async function loop(limit, step) {
// Log the first step and then...
console.log(step);
// ...loop over the rest of the numbers calling
// the delay function on each iteration
for (let i = step + step; i <= limit; i += step) {
await delay(step);
console.log(i);
}
}
const limit = 10;
const step = 2;
loop(limit, step);
You don't need to use both setInterval() and setTimeout(). Use setInterval(), with the interval being the delay converted to milliseconds. At each repetition, subtract the delay from the total number of times, and when this reaches the end, stop the timer.
let time = 10;
let delay = 3;
const run = setInterval(() => {
console.log("Execute event....");
time -= delay;
if (time < delay) {
console.log("Done");
clearInterval(run);
}
}, delay * 1000);

How to run multiple timers one after another with pause in javacript?

I need to run multiple timer one after another with pause. Input for timer comes from the array which contains set of times. I did something with reduce method. But could not break early the reduce method.
const times = [5, 4, 5];
function countDown(secs) {
console.log(secs);
if (secs === 1) {
clearTimeout(timer);
return timer;
}
secs--;
var timer = setTimeout(() => countDown(secs), 1000);
}
times.reduce((totalDelay, time) => {
setTimeout(() => {
countDown(delay);
}, 1000 * totalDelay);
const delay = parseInt(time);
totalDelay += delay;
return totalDelay;
}, 0);
I tried with a boolean to pause and play. The timer is paused by that boolean variable but when I resume two timer is running because of the reduce.
Is there any other way to do this with loop or something?
You code looks way too complicated. All you have to do is setting the timeout inside a function (without arguments) and use that function as a callback to setTimeout.
const times = [5, 4, 5];
function countDown() {
const secs = times.shift();
if (typeof secs === 'number') {
setTimeout(countDown, secs * 1000);
console.log(`Next timer in ${secs} seconds`);
} else {
console.log('Done');
}
}
countDown();
Here I'm removing an element from the start of the times array on each iteration. You could also leave the array unchanged and use an index to keep track of the progress.

Could multiple JS async function reading and modifying same variable causes race condition/unexpected variable value? Need come advice

I wanna keep the variable "remaining" to be >= 0. If it reaches 0, should stop doing any subtraction operation.
There are2 async functions will check the "remaining" and do subtraction if and only if >= 0.
Obviously it does work as my expectation, but I really have no idea how to solve it in the right way...
I read the article https://dev.to/loweisz/how-to-avoid-race-conditions-using-asynchronous-javascript-j30 but I am not sure if it works. Isn't same problem might still happend in between "if (!blocked) {" and "blocked = true;"?
I need help...The following is my test code
let remaining = 1000; // remaining should always be >= 0
const fn1 = async () => {
console.log('fn1 invoked');
while(remaining > 0) {
await new Promise(resolve => {
setTimeout(() => {
// This is just a simulation of the delay between "while(remaining > 0) {" and "remaining = remaining - 1;"
resolve();
}, 1);
});
remaining = remaining - 1;
console.log(`fn1 remaining: ${remaining}`);
}
};
const fn2 = async () => {
console.log('fn2 invoked');
while(remaining > 0) {
await new Promise(resolve => {
setTimeout(() => {
// This is just a simulation of the delay between "while(remaining > 0) {" and "remaining = remaining - 1;"
resolve();
}, 1);
});
remaining = remaining - 1;
console.log(`fn2 remaining: ${remaining}`);
}
};
/**
* run async functions
*/
fn1();
fn2();

Confusion of setInterval with setTimeout in JavaScript

I am trying to use setInterval with setTimeout in JavaScript.
var temArr = []
let timer = setInterval(() => {
var target = Math.random()
temArr.push(target)
console.log(6, target, temArr[temArr.length - 1], target === temArr[temArr.length - 1]) // one
setTimeout(() => {
console.log(9, target, temArr[temArr.length - 1], target === temArr[temArr.length - 1]) // two
}, 500);
}, 100)
setTimeout(() => {
clearInterval(timer)
}, 10000);
I expected that the last value of one and two would be true.
I expected that every time when the logger print, both one and two have the same value of the third parameter, that means target equals temArr[temArr.length - 1]
But the result is that one is true, and two is false
But the intermediate result of two is false, and the last value is true, while one is alway true
When I switch the timeout value, and the result is true that means set the interval with value 500 and the timeout with value 100, at this time both target === temArr[temArr.length - 1] is alway true every time the logger print
could you please tell me why?
They both returned true for me, just different order:
Ok setInterval VS setTimeout:
setInterval - will run the function inside EVERY x ms. which means:
setInterval(() => {
console.log('I will run every 100ms')
}, 100)
Will run EVERY 100ms.
setTimeout - will run the function inside AFTER x ms. which means:
setTimeout(() => {
console.log('I will run after 10 seconds')
}, 10000);
will run AFTER 10 seconds.
So doing this:
let timer = setInterval(() => {
console.log('I will run every 500ms';
setTimeout(() => {
cosole.log('I will run AFTER 500ms EVERY 500ms');
}, 500);
}, 100)
The log inside setTimeout will run 500ms AFTER the first log has been triggered and will trigger every 100ms.
EDIT - Answer to user's edited question:
For clearer logs, I modified your code:
var temArr = []
var intervalOrder = 0;
var timeoutOrder = 0;
var timer = setInterval(() => {
var target = Math.random()
temArr.push(target)
intervalOrder++
console.log('ONE - SET TIMEOUT: ', {order: intervalOrder, target, tem: temArr[temArr.length - 1], result: target === temArr[temArr.length - 1]})
setTimeout(() => {
timeoutOrder++
console.log('TWO - SET TIMEOUT: ', {order: timeoutOrder, target, tem: temArr[temArr.length - 1], result: target === temArr[temArr.length - 1]}) // two
}, 500);
}, 100)
setTimeout(() => {
clearInterval(timer)
}, 1000);
Notice how TWO - SET TIMEOUT doesn't fetch the same result as ONE - SET TIMEOUT? It's because it doesn't fetch the value of that variable when it was called but it fetches ON TRIGGER which means that the value already changed because setInterval has shorter time.
What you can do for this is to call the setTiemout on a different function so it will reference its value on the parameter instead of the newly generated one.
var temArr = []
var intervalOrder = 0;
var timeoutOrder = 0;
var logSecond = (target) => {
setTimeout(() => {
timeoutOrder++
console.log('TWO - SET TIMEOUT: ', {order: timeoutOrder, target, tem: temArr[temArr.length - 1], result: target === temArr[temArr.length - 1]}) // two
}, 500);
}
var timer = setInterval(() => {
var target = Math.random()
temArr.push(target)
intervalOrder++
console.log('ONE - SET TIMEOUT: ', {order: intervalOrder, target, tem: temArr[temArr.length - 1], result: target === temArr[temArr.length - 1]})
logSecond(target)
}, 100)
setTimeout(() => {
clearInterval(timer)
}, 1000);
^^ on the snippet above, the target and result are now the same for both logs.

The differences of "setInterval" between chrome and other browsers, Why?

CODE 1
console.log('start');
const interval = setInterval(() => {
console.log('setInterval');
}, 0);
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve()
.then(() => {
console.log('promise 3');
})
.then(() => {
console.log('promise 4');
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2');
Promise.resolve()
.then(() => {
console.log('promise 5');
})
.then(() => {
console.log('promise 6');
})
.then(() => {
clearInterval(interval);
});
}, 0);
});
}, 0);
Promise.resolve()
.then(() => {
console.log('promise 1');
})
.then(() => {
console.log('promise 2');
});
The result of this code on Windows Edge/Mac Safari/Opera browsers is as follow:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
But when ran on chrome on Windows or Mac, the result have two cases.
Sometimes the result is same as above.
Sometimes the result outputs two setInterval as follow:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setInterval
setTimeout 2
promise 5
promise 6
I'm confused about the result.
CODE 2
function expendTime(k){
console.log((new Date()).getTime());
while(k < 10000){
for(var j = 2; j < k; j++){
if(k%j == 0){
break;
}
if(j == k-1){
console.log(k)
}
}
k++;
}
console.log((new Date()).getTime());
}
var t = setInterval(expendTime,15,3);
setTimeout(function() {
clearInterval(t);
},30);
.as-console-wrapper {
max-height: 100% !important;
}
The result of this code when ran on chrome and others browsers is different.
I consider the result on other browsers is correct, since it is coincides with my common sense.
Yes chrome's* implementation of setInterval does self-correct itself so its calls do fire at exact intervals, removing any drift.
*and maybe Edge's one ?
Here is an example code which does implement such a drift-correcting setInterval method, ran in chrome, you can see it has the same effect than the builtin one, while other implementations and recursive setTimeout will keep growing the drift.
// drift-correcting setInterval
// returns an object which "_id" property is the inner timeout id, so it can be canceled by clearInterval
function driftCorrectingInterval(cb, delay) {
var begin = performance.now(), // what time is it?
result = {
_id: setTimeout(inner, delay)
},
passed = true; // a flag to avoid try-catch the callback
return result;
function inner() {
if (!passed) return; // 'cb' thrown
passed = false; // set up the callback trap
var now = performance.now(),
drift = (now - begin) - delay;
begin += delay; // start a new interval
result._id = setTimeout(inner, delay - drift);
// call it at the end so we can cancel inside the callback
cb();
passed = true; // didn't throw we can continue
}
}
// snippet-only tests
function test(ms) {
function setTimeoutLoop(cb, ms) {
function loop() {
cb();
setTimeout(loop, ms);
}
setTimeout(loop, ms);
}
var now = performance.now(),
built_in_prev = now,
timeout_prev = now,
sCI_prev = now,
built_in_elem = document.querySelector('#test_' + ms + ' .delay.built_in'),
timeout_elem = document.querySelector('#test_' + ms + ' .delay.timeout'),
sCI_elem = document.querySelector('#test_' + ms + ' .delay.sCI');
setInterval(() => {
var now = performance.now(),
delay = (now - built_in_prev) - ms;
built_in_prev += ms;
built_in_elem.textContent = Math.round(delay);
}, ms);
setTimeoutLoop(() => {
var now = performance.now(),
delay = (now - timeout_prev) - ms;
timeout_prev += ms;
timeout_elem.textContent = Math.round(delay);
}, ms);
driftCorrectingInterval(() => {
var now = performance.now(),
delay = (now - sCI_prev) - ms;
sCI_prev += ms;
sCI_elem.textContent = Math.round(delay);
}, ms);
}
test(1000);
[id^='test'] {
border: 1px solid;
padding: 0 12px
}
<div id="test_1000">
<p>built in setInterval drift: <span class="delay built_in">0</span>ms</p>
<p>built in setTimeout loop drift: <span class="delay timeout">0</span>ms</p>
<p>driftCorrectingInterval drift: <span class="delay sCI">0</span>ms</p>
</div>
Note that current specs read as Firefox's implementation, and as what you understood, i.e a recursive setTimeout, without any consideration for drift.
Indeed the timer initialization steps requires that when the repeat flag is set to true, the same arguments should be passed to the next timer initialization steps method.
But an open issue discusses this very problem and might lead to a revision of the specs so that UAs are encouraged to apply such drift-correction when possible.

Categories

Resources