8

I'm still a Promise noob and am trying to figure out how to have my Promise repeat itself.

have an ES6 promise that rejects if some global flag is not set. I need it to retry every 500ms until either:

  • the promise returns a resolve,
  • or a max number of attempts are reached (lets say 10).

Since Promises are async, I don't really want to use a setInterval() check, because I don't think that'd work correctly with asynchronous code. I need the checking to terminate as soon as the promise is resolved successfully ( or timeout reached ).

I'm using ES6 + React + ES6 Promises (so no Q or Bluebird-specific answers, please!)

http://jsfiddle.net/2k2kz9r9/8/

// CLASS
class Test extends React.Component {
    constructor() {
      this.state = {
        status: 'setting up..',
      }
    }
    componentDidMount() {
      // TODO: how do I get this to loop with a timeout?
      this.createSlot()
        .then((slot) => {
          this.setState({
            status: slot
          });
        })
        .catch((e) => {
          this.setState({
            status: e.message
          });
        })
    }
    createSlot() {
      return new Promise((resolve, reject) => {
        if (!this.checkIsReady()) {
          reject(new Error('Global isnt ready yet'));
        }
        // more stuff here but going to resolve a string for simplicity sake
        resolve('successful!');
      });
    }
    checkIsReady() {
      return window.globalThing && window.globalThing === true;
    }
    render() {
        return ( <div>{this.state.status}</div> );
    }
}





    // RENDER OUT
    React.render(< Test/> , document.getElementById('container'));

EDIT: function based on current feedback:

  createSlot(tries) {
    const _this = this;
    return new Promise(function cb(resolve, reject) {
      console.log(`${tries} remaining`);
      if (--tries > 0) {
        setTimeout(() => {
          cb(resolve, reject);
        }, 500);
      } else {
        const { divId, adUnitPath } = _this;
        const { sizes } = _this.props;

        // if it's not, reject
        if (!_this.isPubadsReady()) {
          reject(new Error('pubads not ready'));
        }
        // if it's there resolve
        window.googletag.cmd.push(() => {
          const slot = window.googletag
            .defineSlot(adUnitPath, sizes, divId)
            .addService(window.googletag.pubads());
          resolve(slot);
        });
      }
    });
  }
tdc
  • 5,174
  • 12
  • 53
  • 102
  • 1
    Research "recursive functions". Basically, in `catch`, I'd suggest waiting (via setTimeout) for a period of time, then just call `componentDidMount` again. You won't need to do any special "terminating", as it won't be called if the promise resolves. – Heretic Monkey Oct 14 '16 at 16:22
  • Hi @MikeMcCaughan, thanks for the help. So the problem I see with that approach is it'll call forever if the global fails to get set. Also, in React `componentDidMount` isn't supposed to be invoked manually ( although we could work around that -- the biggest issue is the first part I believe ) – tdc Oct 14 '16 at 16:26
  • See [multiple, sequential fetch() Promise](http://stackoverflow.com/questions/38034574/multiple-sequential-fetch-promise/) – guest271314 Oct 14 '16 at 16:32
  • Yeah, you'd just need to track the number of attempts. I figured you could figure that part out :). – Heretic Monkey Oct 14 '16 at 16:49

2 Answers2

8

As Mike McCaughan mentioned, you can use setTimeout to create a delay between attempts. Resolve or reject your promise once it succeeds or you run out of attempts.

function createPromise(tries, willFail) {
  return new Promise(function cb(resolve, reject) {
    console.log(tries + ' remaining');
    if (--tries > 0) {
      setTimeout(function() {
        cb(resolve, reject);
      }, 500);
    } else {
      if (willFail) {
        reject('Failure');
      } else {
        resolve('Success');
      }
    }
  });
}

// This one will fail after 3 attempts
createPromise(3, true)
  .then(msg => console.log('should not run'))
  .catch(msg => {
    console.log(msg);
    
    // This one will succeed after 5 attempts
    return createPromise(5, false);
  })
  .then(msg => console.log(msg))
  .catch(msg => console.log('should not run'));
Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
  • 2
    Upvote since you mentioned my name :). – Heretic Monkey Oct 14 '16 at 16:51
  • Great answer! I think I'm close with this.. it appears to me I'd put my resolve/reject logic inside the `else` statement? but this causes the tries to count down and only once `tries !>0` does it perform the resolve/reject check.. Shouldn't the check be performed every iteration? – tdc Oct 14 '16 at 16:57
  • @MikeMcCaughan + Mike C I've updated my question with the current state of my function using your feedback. – tdc Oct 14 '16 at 16:59
  • @Prefix There's on possible problem with your update. You're adding the `resolve` to the anonymous function you're pushing in to `window.googletag.cmd`. Might want to move that to *after* the push. – Mike Cluck Oct 14 '16 at 17:01
  • 1
    This was a really helpful answer. Thank you for both the quality of explanation, as well as friendliness from both of you (seems rare on SO these days!). Cheers! – tdc Oct 14 '16 at 17:30
  • 1
    Clearer and more practical example than original-duplicated-accepted-question. Bravo! – Xopi García Nov 19 '20 at 16:59
0

you can try chaining the calls, as promises are supposed to be, this is a bit contrived but I hope you get my drift:

PS attaching global objects to windows is a bad idea and should not be done if at all possible, this was only showing a quick example using your flow...

window.attempts = 0;
window.maxAttempts = 10;
window.globalThing = true;
function createSlot() {

return new Promise((resolve, reject) => {
    if (!this.checkIsReady()) {
      reject(new Error('Global isnt ready yet'));
    }
    // more stuff here but going to resolve a string for simplicity sake
    resolve('successful!');
}).then((pass) => {
    return pass;
  }, (fail) => {
    window.attempts ++;
    //If within attempts, try again
    if(window.attempts < window.maxAttempts){
      //Chain a new promise to resolve with a timeout of 500ms
      return new Promise((resolve, reject) => {
         setTimeout(() => {
            resolve()
         }, 500);
      }).then(() => {
         //Then try again
         return createSlot();
      })
    }
    else {
      //else fail out with reason
      return fail;
    }
  });
}
longstaff
  • 2,061
  • 1
  • 12
  • 15
  • hi @longstaff -- thanks for the help! This looks like what I'm trying to do, although how would I have a delay between the checks? This seems like it would execute all 10 attempts very quickly. I'd like to wait 500ms or so between checks ( since the global is set as a result of an async script loading in ) – tdc Oct 14 '16 at 16:42
  • @Prefix ok, so chain the promises with a timeout, see edit – longstaff Oct 14 '16 at 16:48