Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 promises with timeout interval

I'm trying to convert some of my code to promises, but I can't figure out how to chain a new promise inside a promise.

My promise function should check the content of an array every second or so, and if there is any item inside it should resolve. Otherwise it should wait 1s and check again and so on.

function get(){
    return new Promise((resolve) => {

      if(c.length > 0){
        resolve(c.shift());

      }else{
        setTimeout(get.bind(this), 1000);
      }

    });

}


let c = [];

setTimeout(function(){
  c.push('test');
}, 2000);

This is how I expect my get() promise function to work, it should print "test" after 2 or 3 seconds max:

get().then((value) => {
  console.log(value);
});

Obviously it doesn't work, nothing is ever printed

like image 738
thelolcat Avatar asked Oct 29 '17 18:10

thelolcat


People also ask

Can we use setTimeout in Promise?

setTimeout() is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise: const awaitTimeout = delay => new Promise(resolve => setTimeout(resolve, delay)); awaitTimeout(300).

How do you wrap a timeout in a Promise?

We can wrap setTimeout in a promise by using the then() method to return a Promise. The then() method takes upto two arguments that are callback functions for the success and failure conditions of the Promise. This function returns a promise.

How do you resolve promises after 2 seconds?

Use setTimeout() to call the promise's resolve function with the passed value after the specified delay .

What comes first setTimeout or Promise?

setTimeout(..., 0) is called before Promise.


2 Answers

setTimeout has terrible chaining and error-handling characteristics on its own, so always wrap it:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

function get(c) {
  if (c.length) {
    return Promise.resolve(c.shift());
  }
  return wait(1000).then(() => get(c)); // try again
}

let c = [];
get(c).then(val => console.log(val));
wait(2000).then(() => c.push('test'));

While you didn't ask, for the benefit of others, this is a great case where async/await shines:

const wait = ms => new Promise(r => setTimeout(r, ms));

async function get(c) {
  while (!c.length) {
    await wait(1000);
  }
  return c.shift();
}

let c = [];
get(c).then(val => console.log(val));
wait(2000).then(() => c.push('test'));

Note how we didn't need Promise.resolve() this time, since async functions do this implicitly.

like image 57
jib Avatar answered Oct 19 '22 13:10

jib


The problem is that your recursive call doesn't pass the resolve function along, so the else branch can never call resolve.

One way to fix this would be to create a closure inside the promise's callback so that the recursive call will have access to the same resolve variable as the initial call to get.

function get() {
  return new Promise((resolve) => {
    function loop() {
      if (c.length > 0) {
        resolve(c.shift());
      } else {
        setTimeout(loop, 1000);
      }
    }
    loop();
  });
}

let c = [];
setTimeout(function() {
  c.push('test');
}, 2000);
get().then(val => console.log(val));
like image 35
4castle Avatar answered Oct 19 '22 11:10

4castle