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.
setTimeout()is NOT blocking. It just schedules something to run in the future and then the rest of your code continues to run. That means yourwhileloop keeps running forever setting timers and never giving them a chance to run.