Reputation: 21
I want to hold nodejs execution in setTimeout inside while loop. I have used async- waterfall function but it didn't work inside while loop. so I have used below code :-
var i = 3;
while(i> 0)
{
setTimeout(function(){
console.log('start', i);
setTimeout(function(){ console.log('timeout');}, 1000);
console.log('end', i);
i--;
}, 1000);
}
console.log("execution ends");
But I didn't get the expected output. My Expected output will be like :-
start3
timeout
end3
start2
timeout
end2
start1
timeout
end1
execution ends
Upvotes: 1
Views: 6468
Reputation: 5309
way 1:use while loop
var i = 3
var p = Promise.resolve(i)
while (i > 0) {
(i => {
p = p.then(() => {
return new Promise(function (resolve, reject) {
console.log('start', i)
setTimeout(function () {
console.log('timeout')
console.log('end', i)
resolve()
}, 1000)
})
})
})(i)
i--
}
p = p.then(data => console.log('execution ends'))
way2:
function test(i) {
console.log('start', i)
setTimeout(() => {
console.log('timeout')
console.log('end', i)
i--
if (i < 0) {
return
}
test(i)
}, 1000)
}
test(3);
way3:use async
async function test (i) {
console.log('start', i)
await new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('timeout')
console.log('end', i)
i--
if (i < 0) {
reject(i)
}
resolve(i)
}, 1000)
})
.then(i => test(i))
.catch(i => console.log('done', i))
}
test(3)
Upvotes: 2
Reputation: 707158
First off, you have to understand that setTimeout()
in Javascript is non-blocking. That means that all it does is schedule something to run later and then the rest of your code immediately keeps on running.
In your particular code example, you will have an infinite loop because the while loop keeps going forever, continuing to schedule more and more timers, but until the while loop stops, none of those timers can run. And, until one of those timers can run, your loop variable i
never gets changed so the while
loop never stops.
To understand why it works this way you really have to understand the event-driven design of Javascript and node.js. When you call setTimeout()
, it schedules an internal timer inside of the JS engine. When that timer fires, it inserts an event in the Javascript event queue. The next time the JS interpreter is done with what it was doing, it will check the event queue and pull the next event out of the queue and run it. But, your while
loop never stops going so it can't ever get to any new events, thus it can never run any of your timer events.
I will show you three different ways to generate your desired output and they are all in runnable code snippets so you can run them right in the answer to see their results.
The first technique is accomplished in plain Javascript just by setting timers at different times from the future such that the various desired outputs trigger in the right sequence:
Varying Timers
let cntr = 3;
for (let i = 1; i <= 3; i++) {
// schedule timers at different increasing delays
setTimeout(function() {
console.log('start', cntr);
setTimeout(function() {
console.log('timeout');
console.log('end', cntr);
--cntr;
if (cntr === 0) {
console.log("execution ends");
}
}, 1000);
}, i * 2000);
}
Or, if you really want it to be a while
loop:
let cntr = 3, i = 1;
while (i <= 3) {
// schedule timers at different increasing delays
setTimeout(function() {
console.log('start', cntr);
setTimeout(function() {
console.log('timeout');
console.log('end', cntr);
--cntr;
if (cntr === 0) {
console.log("execution ends");
}
}, 1000);
}, i * 2000);
i++;
}
Using Promises to Sequence Operations
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
function run(i) {
return delay(1000).then(() => {
console.log("start", i);
return delay(1000).then(() => {
console.log("timeout");
console.log("end", i);
return i - 1;
});
});
}
run(3).then(run).then(run).then(() => {
console.log("execution ends");
});
This could also be put into a loop if you wanted:
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
function run(i) {
return delay(1000).then(() => {
console.log("start", i);
return delay(1000).then(() => {
console.log("timeout");
console.log("end", i);
return i - 1;
});
});
}
[3, 2, 1].reduce((p, v) => {
return p.then(() => {
return run(v);
});
}, Promise.resolve()).then(() => {
console.log("execution ends");
});
Promises Plus ES7 Async/Await
This method uses the await
feature of ES7 to allow you to write sequential-like code using promises and await
which can make these kinds of loops a lot simpler. await
blocks the internal execution of a function while it waits for a promise to resolve. It does not block the external return of the function. the function still returns immediately. It returns a promise that resolves when all the blocked pieces of the internal function are done. That's why we use .then()
on the result of runSequence()
to know when it's all done.
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
async function runSequence(num) {
for (let i = num; i > 0; i--) {
await delay(1000);
console.log("start", i);
await delay(1000);
console.log("timeout");
console.log("end", i);
}
}
runSequence(3).then(() => {
console.log("execution ends");
});
This await
example illustrates how promises and await
can simplify the sequencing of operations in ES7. But, they still require you to understand how asynchronous operations work in Javascript, so please don't attempt to skip that level of understanding first.
Upvotes: 0
Reputation: 1000
Please try below snippet. You can introduce API calls OR async calls in place of setTimeout in timer method.
const timer = () => {
return new Promise(res => {
setTimeout(() => {
console.log('timeout');
res();
}, 1000);
});
}
let i = 3;
let temp;
while (i > 0) {
if (temp !== i) {
temp = i;
console.log('start', temp);
timer().then(() => {
console.log('end', temp);
i -= 1;
});
}
}
Upvotes: 0
Reputation: 2742
There are a few issues with your program w.r.t to your expected output. The first i-- will work only after 1000ms. By that time, too many while loops would have run. Also setTimeOut
is non-blocking ,hence ,the execution ends
will be consoled even before the first start
is consoled . To reach your expected outcome, you can make a few changes to the code :
var i = 3;var j =3;
while(j> 0)
{
setTimeout(function(){
console.log('start', i);
console.log('timeout');
console.log('end', i);
i--;
}, 1000);
j--;
}
if(j == 0){
setTimeout(function(){
console.log('execution ends');
}, 1000);
}
Upvotes: 0
Reputation: 51
you didn't got the expected output because there is closure in your code,improve your code like this:
var i = 3;
while(i> 0)
{
setTimeout((
function(i){
return function(){
console.log('start', i);
setTimeout(function(){ console.log('timeout');}, 1000);
console.log('end', i);
i--;
}
}
)(i), 1000);
}
Upvotes: -1
Reputation: 3568
nodejs is async by nature and setTimeout
is more or less like executing something on a new thread (more or less because JS is single threaded and uses event loop).
Check out this npm package : https://www.npmjs.com/package/sleep
That should do the trick...
But obviously you should use sleep
only for debugging purposes.
In production code you'd better embrase the async nature of NodeJS and use Promises
Check out this for more details: event loop : https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
setTimeout : https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
Promise : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Upvotes: 0