I had the following question on an interview. cannot see what's wrong with it.
The following code attempts to run 10 tasks sequentially and have a timeout of 1 second after each task. Given the following piece of code, please identify issues with it, and write a correct implementation for the original intention.
for (let i = 0; i < 10; i++) {
setTimeout(function() {
// Running some asynchronous tasks here
// ...
console.log("completed task id " + i);
}, 1000)
}
You can try with async function awaiting for promise will be resolved.
async function toDo() {
for (let i = 0; i < 10; i++) {
await new Promise(res => {
setTimeout(() => {
// Running some asynchronous tasks here
// ...
console.log('completed task id ' + i)
res()
}, 1000)
})
}
}
toDo()
This starts all the timeouts immediately when the loop runs, so all the messages will be in 1 second, instead of 1 second between them.
One way to fix it is to make the timeouts depend on i.
for (let i = 0; i < 10; i++) {
setTimeout(function() {
// Running some asynchronous tasks here
// ...
console.log("completed task id " + i);
}, 1000 * (i + 1))
}
This is the simplest way to do it, it starts each task 1 second after the previous one started.
But if the tasks performed some long operation, and you didn't want to start the next task until 1 second after the previous one completed, you would need a more complex implementation. Instead of using a loop, you would have to have the task increment i and call setTimeout() itself to schedule the next iteration.
function timeout_fn(i) {
// do slow operation
console.log("completed task id " + i);
i++;
if (i < 10) {
setTimeout(() => timeout_fn(i), 1000);
}
}
setTimeout(() => timeout_fn(0), 1000);
Issues:
setTimeout schedules a function to be run after the specified delay. Note that all setTimeouts are scheduled at the same time, they don't wait for each other. This is what is meant by "asynchronous". Thus, all of your setTimeout calls are with a delay of 1000 ms, and all of them will run consecutively after only 1000ms.
If you really are running other asynchronous tasks in the callback to setTimeout, the console.log will not wait for them unless you tell it to. I can only guess, but make sure you use an await, an async function or another callback.
for (let i = 0; i < 10; i++) {
setTimeout(function() {
// Running some asynchronous tasks here
// ...
console.log("completed task id " + i);
}, 1000 * i) //multiply i by 1000 to wait 1,2,3,4... seconds
}
Maybe another solution will be to use the setInterval function with a 1000ms interval and call setTimeout with 10 seconds waiting times to clear the interval :
const myInterval = setInterval(function() {
// some async stuff here
}, 1000);
setTimeout(function() {
clearInterval(myInterval)
}, 10000);
Related
I try to implement a loop in my noejs app that will always wait between the tasks. For this I found the setInterval function and I thought it is the solution for me. But as I found out, the first Interval, means the very first action also wait until the interval is ready. But I want that the first action runs immediatly and then each action with the given interval.
In arry scope:
myArray[0] starts immediatly while myArray[1..10] will start with Interval waiting time.
I tried it with:
function rollDice(profilearray, browserarray, url) {
return new Promise((resolve, reject) => {
var i = 0;
const intervalId = setInterval(
(function exampleFunction() {
console.log(profilearray[i].profil);
//########################################################################
createTestCafe("localhost", 1337, 1338, void 0, true)
.then((tc) => {
testcafe = tc;
runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
})
.then(() => {
return runner
.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
.then((failedCount) => {
testcafe.close();
if (failedCount > 0) {
console.log(profilearray[i].profil);
console.log("No Success. Fails: " + failedCount);
//clearInterval(intervalId);
//reject("Error");
} else {
console.log(profilearray[i].profil);
console.log("All success");
//clearInterval(intervalId);
//resolve("Fertig");
}
});
})
.catch((error) => {
testcafe.close();
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
//clearInterval(intervalId);
//reject("Error");
});
//######################################################################
i++;
console.log("Counter " + i);
if (i === profilearray.length) {
clearInterval(intervalId);
resolve("Fertig");
}
return exampleFunction;
})(),
3000
); //15 * 60 * 1000 max time to wait (user input)
});
}
The way I have done works bad because in the first action it will not start the testcafe. But in all other actions it will do.
Anybody knows a better way to do this?
Scope:
Give a array of data and for each array start testcafe with a given waiting time. 3 seconds up to 15 minutes. Because in some cases 15 Minutes is a long time I want to start the first one without any waiting time.
Iam open for any suggestion
For modern JavaScript await and async should be used instead of then and catch.
This will make many things easier, and the code becomes more readable. You e.g. can use a regular for loop to iterate over an array while executing asynchronous tasks within it. And use try-catch blocks in the same way as you would in synchronous code.
// a helperfunction that creates a Promise that resolves after
// x milliseconds
function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
async function rollDice(profilearray, browserarray, url) {
for (let i = 0; i < profilearray.length; i++) {
// depending how you want to handle the wait you would create
// the "wait"-Promise here
// let timer = wait(3000)
let testcafe = await createTestCafe("localhost", 1337, 1338, void 0, true);
try {
let runner = testcafe.createRunner();
inputStore.metaUrl = url;
inputStore.metaLogin = teamdataarray[0].email;
inputStore.metaPassword = teamdataarray[0].password;
inputStore.moderator = profilearray[i].profil;
inputStore.message = profilearray[i].template;
inputStore.channelid = profilearray[i].channelid;
let failedCount = await runner.src([__basedir + "/tests/temp.js"])
.browsers(browserarray)
.screenshots("", false)
.run()
if (failedCount > 0) {
// ...
} else {
// ...
}
} catch (err) {
console.log(profilearray[i].profil);
console.log("Testcafe Error" + error);
} finally {
testcafe.close();
}
// Here you would wait for the "wait"-Promise to resolve:
// await timer;
// This would have similar behavior to an interval.
// Or you wait here for a certain amount of time.
// The difference is whether you want that the time the
// runner requires to run counts to the waiting time or not.
await wait(3000)
}
return "Fertig"
}
Declare function before setInterval, run setInterval(exampleFunction, time) and then run the function as usual (exampleFunction()). May not be ideal to the millisecond, but if you don't need to be perfectly precise, should work fine.
Ask if you need further assistance
EDIT: now looking twice at it, why are you calling the function you pass as parameter to setInterval inside the setInterval?
I am trying to use setInterval to call an API that has rate limit for requests per seconds.
I tried to use setInterval as a while loop, but I can't find info on when it starts the next round.
So if I do:
setInterval(() => {
CallAPI();
// Some proccessing
}, 300)
Will the next round start after the first one finishes or regardless of it? because order matter, and also timing can cause an error.
I can also try to do the same with setTimeout and a while loop like so:
while(true) {
setTimeout(() => {
CallAPI();
// do some proccessing
}), 300
}
But again, I am not sure when the next round of the loop will start.
And lastly I cheat my way through it and do
while(true) {
await CallAPI();
// Do proccessing
await setTimeout(() => true, 300)
}
So what is the order for setTimeout() and setInterval() in a loop?
Just call it when it is done executing. No need for a loop.
function fakeCall () {
return new Promise((resolve) => {
window.setTimeout(resolve, Math.random() * 2);
});
}
function next() {
window.setTimeout( async function () {
await fakeCall();
console.log(Date.now());
next();
}, 3000);
}
next();
You'll probably want to set up your loop as follows:
var TO_asyncLoop = null;
function asyncLoop(fn, ms){
clearTimeout(TO_asyncLoop);
TO_asyncLoop = setTimeout(() => { fn() }, ms)
}
var i = 0;
function doSomething(){
// do somthing
console.log("hi", i++);
// when this "something" is finished, do it again:
asyncLoop(doSomething, 500)
}
doSomething()
As we may know, var keyword defines a variable globally, or locally to an entire function regardless of block scope. So the below code will log 5 times with the same value.
for(var i = 0; i < 5; i++){
setTimeout(() => console.log(i), 2000);
}
To visualize the above JS runtime like this
As you can see, 5 tasks in Callback Queue will wait until Call stack is empty. So after the synchronous loop is done - It means Call stack is empty in my case, then 5 scheduled tasks - console.log(i) with the value of i is equal to 5 will be executed. You can play around here
And what I want is to log right after i == 2. It works as I expected.
var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync(){
for(var i = 0; i < 5; i++){
if(i == 2) await sleep(2000);
setTimeout(() => console.log(i));
}
}
runAsync();
But I'm curious how it works while I'm not sure the call stack is empty? Whether when I'm using await, allowing the caller of the async function to resume execution, or some else?
Any explanation/visualization of image flow would be appreciated. Thanks.
await cedes control of the thread and allows other processes to run until the promise being awaited resolves. Even if the promise is already resolved, await will yield to any "microtasks" that have been waiting to execute, but that's a moot point in your case because your promise takes a full two seconds to resolve.
In your case, two setTimeouts are queued up before the await, so they are allowed to run when the await happens.
The timeline is basically like this:
i = 0
setTimeout 1 scheduled
i = 1
setTimeout 2 scheduled
i = 2
await
setTimeout 1 callback runs
setTimeout 2 callback runs
setTimeout 3 scheduled
i = 3
setTimeout 4 scheduled
i = 4
setTimeout 5 scheduled
i = 5
loop ends
setTimeout 3 callback runs
setTimeout 4 callback runs
setTimeout 5 callback runs
You can see that i is 2 when the first pair of setTimeouts are allowed to execute, and it is 5 when the remaining 3 execute.
Here is a snippet that hopefully demonstrates the process a little better:
var sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(1), ms));
async function runAsync() {
for (var i = 0; i < 5; i++) {
console.log('i is now', i);
if (i == 2) {
console.log('about to sleep');
await sleep(5000);
console.log('now done sleeping');
}
console.log('about to setTimeout. i is', i, 'right now');
setTimeout(() => {
console.log('setTimeout task running:', i, '- scheduling a new timeout.');
setTimeout(() => console.log('inner timeout:', i), 1000);
});
}
console.log('done looping. i is', i);
}
runAsync();
I have a loop that cycles around and creates REST Queries, creating and running a different one each time.
In the future I imagine the number of REST Queries that could be made will increase far higher than the browser will take at once (over 20,000).
I want to do something along the lines of counting how many loops have been done, and after every 500 or so, pausing for a few seconds to allow the browser to catch up with the REST responses, and then continuing.
How is this done in react JS?
example code:
for (var i = 0; i < array.length; i++)
{
query += i;
axious.get(query) . then { ...does stuff here... }
//want something along the lines of if multiple of 500, wait(1000)
}
the simplest approach is to make a waitIf function that returns a Promise and accepts a condition.
If the condition is true, it will wait then execute the callback, otherwise, it will execute the callback directly.
a simple implementation would be.
function waitIf(condition, duration, callback) {
if (condition) {
// return a Promise that wait for a `duration` ms using timeout
return Promise((resolve) => {
setTimeout(() => {
resolve(callback());
}, duration);
})
}
return callback();
}
for (let i = 0; i < array.length; i++) {
query += i;
// unify the call here
waitIf(i % 500 === 0, 1000, () => axious.get(query)).then();
}
I have an array with multiple delays stored in it: myArray[8000,4000,3000,6000,5000]. I need for setTimeout/setInterval to run each delay and then execute a specific piece of code. So for example:
myArray[0]=8000;
myArray[1]=4000;
myArray[2]=3000;
myArray[3]=6000;
myArray[4]=5000;
for(var k=0;k<5;k++)
{
setTimeout(function() {
console.log("CODE TO BE EXECUTED");
}, diffArray[k]);
}
So the end result would be wait 12 seconds run code, wait 4 seconds run code, wait 3 seconds run code wait 6 seconds run code, and wait 5 seconds and run code. The current code runs them all at the same time.
I know that this most likely needs to be done with recursion, I've tried a few things as far as getting it to work, but no luck. I got it to work one way but it unfortunately locked the UI thread which I will need to perform other actions in the UI while this is running. Any help with this would be greatly appreciated!
Run setTimeout sequentially. You are running all setTimeout at same time.
myArray[0]=8000;
myArray[1]=4000;
myArray[2]=3000;
myArray[3]=6000;
myArray[4]=5000;
var k =0;
function repeat(){
if(k == myArray.length) return;
setTimeout(function() {
repeat();
console.log("CODE TO BE EXECUTED");
}, myArray[k]);
k++;
}
You should make another function so that the k variable is copied each time because setTimeout is an async operation:
var func = function(k)
{
setTimeout(function() {
console.log("CODE TO BE EXECUTED");
}, diffArray[k]);
}
for(var k=0;k<5;k++)
{
func(k)
}
You mention 12 seconds, but there is not a value that large. If you want the code to run 8sec, 12sec,15sec,21sec, and 26sec, then:
var delay =0;
for(var k=0;k<5;k++){
delay+=myArray[k];
setTimeout(function() {
console.log("CODE TO BE EXECUTED");
}, delay);
}