Ketan Bodarya
Ketan Bodarya

Reputation: 21

Use setTimeout inside while loop in Node.js

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

Answers (6)

xianshenglu
xianshenglu

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

jfriend00
jfriend00

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

Rushikesh Bharad
Rushikesh Bharad

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

UchihaItachi
UchihaItachi

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

zero
zero

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

ben
ben

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

Related Questions