Using setInterval inside javascript closures - javascript

I am new to javascript and reading about closures currently. I was trying to implement an example to better understand the concept.
This is the snippet:
function getValue() {
let a = 0
const runner = async () => {
a += 1
}
setInterval(runner, 100)
return () => {
return a
}
}
let value = getValue()
console.log(value()) -----> logging statement 1
// some long operation
console.log(value()) -----> logging statement 2
However, the output that I see is:
1
1
I expected the two logging statements to output two different values. My understanding is that once getValue() is called, the runner function would execute at regular intervals. The variable a would get updated and hence the 2nd console.log should have printed an incremented value of a.

You are almost there except missed the main part.
Operations are happening too fast. It does not take 100 milliseconds to run the second iteration so the value remains the same.
Try to wait at least 100 milliseconds since the interval you have needs that amount of time to increment.
As shown in the example below.
function getValue() {
let a = 0
const runner = async () => {
a += 1
}
setInterval(runner, 100)
return () => {
return a
}
}
let value = getValue()
console.log(value())
// operations are happening too fast
setTimeout(() => console.log(value()), 100);
setTimeout(() => console.log(value()), 200);
setTimeout(() => console.log(value()), 300);

function getValue() {
let a = 0
const runner = () => {
a += 1
console.log("Runner's A: " +a)
}
setInterval(runner, 100)
return () => {
return a
}
}
let value = getValue()
console.log(value());// -----> logging statement 1
// some long operation
console.log(value());// -----> logging statement 2
https://jsbin.com/geponejuba/edit?js,console,output
as pointed out above js is single threaded, and so the way it works is, your log statements are executed 1st and if there is nothing else to do, then setTimeout/setInterval are executed (this is a extremely simplified statement, You can read about "Event loop" for a detailed version)
on the example jsbin i shared you can see the setInterval is getting called and is indeed updating the value.

Related

Is there a way to alternate setInterval durations?

I want to be able to call setInterval (or something similar) at two different lengths, alternating.
For example, running a function after 5 seconds, then 1 second, then 5 seconds again, and so on.
Is this possible? I tried a function that alternates the value, but it didn't seem to work.
let num = 5000
function alternateNum() {
if (num === 5000) { num = 1000 }
else { num = 5000 }
}
setInterval(() => {
// ...
alternateNum()
}, num);
JS timers have a very complicated history.
Using a recursive setTimeout invocation is a simple and elegant solution as long (as your runtime implements tail call optimization).
Separate from the issue of recursion is the issue of timer drift. This is covered in the YouTube video JavaScript counters the hard way - HTTP 203 if you'd like an accessible introduction.
In many JS engines (e.g. V8) setInterval will handle drift correction for you, so there's actually an advantage to using it over recursively invoking setTimeout. (Check the millisecond timestamps in the console messages in the snippet below to verify this.)
In order to determine the constant interval argument you'll need for setInterval, you'll need to find the greatest common factor of your delay durations. Once you have this value, you can use it as the base interval delay, and keep track of your interval state to determine whether you should switch to the next interval delay, run your other code, etc. Here's a minimal example:
const durations = [1000, 5000];
// If you can't determine this in advance and use a constant value,
// then you can calculate it at runtime using a function:
const gcf = 1000; // or const gcf = findGreatestCommonFactor(durations);
let durationIndex = 0;
let elapsed = 0;
function update () {
elapsed += gcf;
const ready = elapsed === durations[durationIndex];
if (ready) {
elapsed = 0;
durationIndex = (durationIndex + 1) % durations.length;
}
return ready;
}
setInterval(() => {
const ready = update();
if (!ready) return;
// Do your interval task, for example:
console.log('tick');
}, gcf);
The problem with setInterval() is that the time is taken into account just once. You can use setTimeout() with recursion instead:
function doAction(flipFlop) {
setTimeout(() => {
console.log(flipFlop ? 'flip' : 'flop');
doAction(!flipFlop);
// do some other action...
}, flipFlop ? 1000 : 3000);
}
doAction(true);
Watch out though if you have a long running process, this recursion gets deeper and deeper.
I think this method is the easiest:
setInterval(() => {
console.log("first");
setTimeout(() => console.log("second"), 750);
}, 2000);
This creates an interval that alternates between 1250 and 750 milliseconds.
The problem with your code
let num = 5000
function alternateNum() {
if (num === 5000) { num = 1000 }
else { num === 5000 }
}
setInterval(() => {
// ...
alternateNum()
}, num);
The last few lines (the setInterval) call are only getting called once with the initial value of num and thus any future changes to num won't be reflected in the setTimeout call.
How to fix it
You should use setTimeout within the function that has your code and call your function recursively:
const doStuff = (time = 1000) => {
// Your code here
// generate the next time to wait
const nextTime = time === 5000 ? 1000 : 5000;
// call the function again after waiting `time` milliseconds
setInterval(() => doStuff(nextTime), time);
}
Then you would call doStuff to start it. If you wanted to start it immediately with the next one happening after 1 second you could do:
doStuff();
Or if you wanted to call it after 5 seconds with the next one happening one second after that:
setTimeout(doStuff, 5000);
The difference here compared to your code is that the variable that represents the time is being used over and over again as it changes instead of just once on initial code execution.

How do I perform a simple toggle operation in JavaScript with the help of setInterval()?

This is what my code looks like:
var fnInterval = setInterval(function() {
let b = true
if (b) {
console.log("hi")
} else {
console.log("bye")
}
b = !b
}, 1000);
clearTimeout(fnInterval, 10000)
I am a newbie to JavaScript and my aim here is to console log a message every 1 second for a total duration of 10 seconds, but during each interval I want my message to toggle its value between "hi" and "bye" . How can I do it? (as of now it displays the value for the default boolean and doesn't change later)
Move the flag variable out of the function:
let b = true;
const fnInterval = setInterval(function() {
if (b) {
console.log("hi");
} else {
console.log("bye");
}
b = !b
}, 1000);
To stop the timer after 10000 milliseconds, wrap the call to clearInterval in a setTimeout:
setTimeout(() => clearInterval(fnInterval), 10000);
Meanwhile, note that the return value of setInterval is not a function but a number, so it may be misleading to call it fnInterval.
First of all, declare let b = true outside of the callback function. It's re-initialized on each call otherwise.
Secondly, the 10000 in clearTimeout(fnInterval, 10000) isn't a valid parameter. clearTimeout(timeoutId) accepts only the first parameter and clears the timeout passed in immediately. You'd need a setTimeout to trigger this after 10 seconds, if that's your goal. But that causes a race condition between the two timeouts -- imprecision can mean you'll miss some of the logs or wind up with extra logs.
Using a counter is one solution, as other answers show, but usually when I'm using complex timing with setInterval that requires clearing it after some number of iterations, I refactor to a generic promisified sleep function based on setTimeout. This keeps the calling code much cleaner (no callbacks) and avoids messing with clearTimeout.
Instead of a boolean to flip a flag back and forth between two messages, a better solution is to use an array and modulus the current index by the messages array length. This makes it much easier to add more items to cycle through and the code is easier to understand since the state is implicit in the counter.
const sleep = ms => new Promise(res => setInterval(res, ms));
(async () => {
const messages = ["hi", "bye"];
for (let i = 0; i < 10; i++) {
console.log(messages[i%messages.length]);
await sleep(1000);
}
})();
setInterval() is stopped by clearInterval() not clearTimeout(). Details are commented in code below.
// Define a counter
let i = 0;
// Define interval function
const fnCount = setInterval(fnSwitch, 1000);
function fnSwitch() {
// Increment counter
i++;
// if counter / 2 is 0 log 'HI'
if (i % 2 === 0) {
console.log(i + ' HI');
// Otherwise log 'BYE'
} else {
console.log(i + ' BYE');
}
// If counter is 10 or greater run fnStop()
if (i >= 10) {
fnStop();
}
};
function fnStop() {
// Stop the interval function fnCount()
clearInterval(fnCount);
};

How to run a delay function for 5 times in JavaScript?

I want to run a delay function for five seconds in JavaScript which will print "Hello" in the console after every second for five times.
Similar Python Code:
from time import delay
for I in range(5):
print("Hello")
delay(1)
The above code will print "Hello" five times with one second delay between each print.
Now I want to do similar kind of operation in JS.
Now in JS we have setTimeout function which will call a function after after a specified time. The following code will print "Hello" in the console after 1 second interval.
setTimeout(function(){
console.log("Hello");
}, 1000);
How can I run this code that will print 'Hello' in the console five times with an one second delay between each print?
NB: I tried to pass this function inside a for loop, but it did not work.
Try like this:
var count = 5;
printWithDelay();
function printWithDelay() {
setTimeout(function() {
console.log("Hello");
count--;
if (0 < count) {
printWithDelay();
};
}, 1000);
};
In JavaScript, 'setTimeout' runs after the code that follows it, so the next iteration of the loop needs to be called from within the callback function for this to work.
JavaScript, unlike PHP and Python, is asynchronous. The event loop is sensitive to anything that blocks it. However, there are ways to achieve what you want.
setInterval
With setInterval, you can build a wrapper it and use it.
function repeatFunction(func, delay, repeat) {
let counter = 0;
let interval = setInterval(() => {
if (repeat !== counter) {
func();
counter++;
} else {
clearInterval(interval)
}
}, delay);
}
repeatFunction(() => console.log(5), 1000, 4)
async/await syntax
The other option is using async/await with Promises (recommended)
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
(async() => {
for (let i = 0; i < 5; i++) {
console.log(i);
await sleep(1000);
}
console.log('done')
})();
Use setInterval, this should get your job done!
const test = setInterval(() => console.log("Hello"), 1000);
And then after 5seconds remove interval
setTimeout( () => clearInterval(test), 5000)
const test = setInterval(() => document.body.innerText += "Hello", 1000);
setTimeout( () => clearInterval(test), 5000);
NOTE: The first console.log() will happen after a second, if you want it immediately just put one console.log() outside the setInterval.

How to run two setTimeout tasks in parallel?

I'm reading YDKJS and early on we are talking about the difference between Async, Parallel and Concurrent Code.
I have a simple Async example:
let output = 0;
const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);
bar( () => {
output = 1;
});
foo( () => {
output = 2
});
setTimeout(() => {
// This Async code should have 2 different outputs
output;
}, 2000);
The above code can have the 2 answers based on the Math.random timer and the mutable output:
However, I'd like to add a bit more complexity and convert foo and bar to run in parallel... I don't have much understanding on how I can achieve this:
Question: How can we update the code below, so that bar and foo are run in parallel and therefore, output has more than 2 possible outcomes?
Note: this is purely for learning purposes... I want to see the race conditions occure.
let inputA = 10;
let inputB = 11;
const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);
bar( () => {
inputA++;
inputB = inputB * inputA;
inputA = inputA + 3;
});
foo( () => {
inputB--;
inputA = 8 + inputB;
inputB = inputA * 2;
});
setTimeout(() => {
// This Parallel code should have more than 2 outputs;
console.log(inputA, inputB);
}, 2000);
The race condition you're looking to invoke isn't possible in ordinary Javascript, luckily. Any time you have a synchronous function, once control flow is passed to that function, that function is absolutely guaranteed to run to the end (or throw) before any other Javascript in the environment runs.
For example, given 1000 tasks which are scheduled by setTimeout to occur in the next 1-2 seconds (randomly), and you also invoke the following function after 1.5 seconds:
const fn = () => {
console.log('1');
for (let i = 0; i < 1e8; i++) {
}
console.log('2');
};
Once fn starts, it will run all of its code (synchronously) before the next random function runs. So even if the random functions call console.log, it is still guaranteed that, in the above situation, 2 will be logged right after 1.
So, in your original example, there are only 2 possibilities:
bar's callback runs first, and completely finishes, then foo's callback runs, and completely finishes. Or:
foo's callback runs first, and completely finishes, then bar's callback runs, and completely finishes.
Nothing else is possible, even if the random timeouts land on exactly the same number. Control flow can only be in exactly one place in the code at any given time.
For your original question, you want to see them run in parallel. You can use Promise.all to run multiple async tasks, it will waits for all async tasks resolved and return an output array.
Promise.all executes async tasks by iterating them(in series), not technically executes them in parallel, but they are running in parallel. It will give you a result when all async tasks resolved or rejected if any one of them failed.
Or you can just run them 1 by 1 without Promise.all. they won't block each other, so still in parallel, but you Promise.all just help you handle callback results in one place.
The output will be either 12 or 20, depends on random timeout you set for bar and foo functions.
For race condition, only the setTimeout functions are asynchronous, but all operations in callbacks are synchronous and non-blocking, so the thread won't jump from operations in one callback to another callback unless all operations in that callback are finished.
But in JS, you can still have data race when using SharedArrayBuffer which needs Atomics object to prevent data race.
let output = 0;
let inputA = 10;
let inputB = 11;
const bar = (cb) => setTimeout(cb, Math.random() * 1000);
const foo = (cb) => setTimeout(cb, Math.random() * 1000);
bar( () => {
inputA++;
inputB = inputA;
output = inputA + 1;
});
foo( () => {
inputB--;
inputA = inputB;
output = inputB * 2;
});
Promise.all([bar(),foo()])
.then(output => console.log('foo and bar tasks finished with output ',output));
setTimeout(() => {
console.log('output variable value: ', output)
}, 2000);

How can I get a function to execute every time it receives output from another function? (NodeJS)

I have some pseudo code below:
function generateNumber() {
const n = Math.floor(Math.random() * 15);
console.log('Original Number')
console.log(n)
return {
n
};
}
function loop() {
const rand = Math.round(Math.random() * (4000 - 600)) + 600;
setTimeout(() => {
generateNumber();
add_1();
loop();
}, rand);
}
function add_1() {
const x = generateNumber() + 1;
console.log('New Number')
console.log(x)
}
loop()
What I want is for add_1() to execute every time generateNumber() outputs a number. So for now, generateNumber() will output a value and it immediately goes to add_1(), where a value of 1 is added to the incoming number, which is then sent back out. The loop() function is there just to get generateNumber() to continuously output values at different times. But in reality it could be that I want a sensor to provide data, and every time it sends a value, I want the data to go into a function (i.e. add_1()).
Is my only option to have generateNumber() and add_1() in a function together? (Which is the case in the pseudo code above)
I want it so that add_1() executes every time it gets input. Preferably without needing add_1() to be in a function, or loop, with generateNumber().
My guess is add_1() has to be an async function and has to wait for some sort of promise from generateNumber().
You can see in the code, that there is nothing to "trigger" add_1() to execute, so I believe my question is how to create this "trigger" based on output from generateNumber().
Monkey-patch generateNumber().
var oldGenerateNumber = generateNumber;
generateNumber = function() {
const ret = oldGenerateNumber();
add_1();
return ret;
};

Categories