ES6 promises with timeout interval

2020-02-07 02:46发布

问题:

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

回答1:

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.



回答2:

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));



回答3:

In the else case, you never resolve that promise. get might create another one, but it is returned to nowhere.

You should promisify your asynchronous function (setTimeout) on the lowest level, and then only chain your promises. By returning the result of the recursive call from a then callback, the resulting promise will resolve with the same result:

function delayAsync(time) {
    return new Promise(resolve => {
        setTimeout(resolve, time);
    });
}
function get(c) {
    if (c.length > 0){
        return Promise.resolve(c.shift());
    } else {
        return delay(1000).then(() => {
            return get(c); // try again
        });
    }
}


回答4:

What you need is a polling service, which checks periodically for specific condition prior proceeding with promise resolution. Currently when you run setTimeout(get.bind(this), 1000); you are creating a new instance of the promise without actually resolving the initial promise, because you don't reference to the initial resolve function that you created.

Solution:

  • Create a new callback function that you can reference to it inside the promise
  • Pass the resolve & reject as params in the setTimeout invocation e.g. setTimeout(HandlePromise, 1000, resolve, reject, param3, param4 ..); setTimeout API

function get() {
  var handlerFunction = resolve => {
    if (c.length > 0) {
      resolve(c.shift());
    } else {
      setTimeout(handlerFunction, 1000, resolve);
    }
  };

  return new Promise(handlerFunction);
}

let c = [];

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

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

  • For more information look into javascript polling article


回答5:

Use setInterval to check every second. Run this script to understand.

let c = [];

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

      var i = setInterval(function(){
        if(c.length > 0){
          resolve(c.shift());
          clearInterval(i);
        }
      }, 1000);

    });
}



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

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