uylmz
uylmz

Reputation: 1552

Wait a maximum of N seconds for something to happen

In my JS code I needed to wait up to 3 seconds for a flag to be true. I did the following:

const waitForSomethingToBeTrue = (something) => {
    const maxWaitingTime = 3 * 1000;

    let promises = [];
    for (let i = 100; i < maxWaitingTime; i += 100) {
        promises.push(new Promise((resolve, reject) => {
            setTimeout(() => {
                if (something()) {
                    resolve();
                } else {
                    reject(something.name + " didn't happen in " + i + "miliseconds.");
                }
            }, i);
        }));
    }

    return Promise.any(promises).catch(() => {
        return Promise.reject(`${something.name} didn't happen in maximum allowed
            time (${maxWaitingTime} msecs).`);
    });
};

As you can notice, there are a couple of problems with my approach:

  1. I'm creating many unnecessary timeouts, for example, if promise at the 200msec mark is resolved, rest of them is unneeded and will already resolve
  2. I'm using Promise.any, which is not officially supported yet. I solved this by writing a shim, using Promise.all reversed, but still, an officially supported method would be better.

how can I solve this problem without having the above issues?

Upvotes: 1

Views: 1267

Answers (4)

Jamiec
Jamiec

Reputation: 136094

I would do this with just a single promise, which either resolve if something is true or reject if it gets to the time

const waitForSomethingToBeTrue = (something) => {
  return new Promise( (resolve,reject) => {
    let start = new Date().getTime();
    const intervalId = setInterval( () => {
      if(something()){
        clearInterval(intervalId);
        resolve();
      }
      else{
        timer = (new Date().getTime() - start);
        if( timer > 3000){
          clearInterval(intervalId);
          reject("took too long");
        }
      }
    },100);
  });
}

// test code below just sets a global bool to true by clicking the button
var globalBool = false;
async function testIt(){
  document.querySelector("#clickme").addEventListener("click",() => globalBool = true);
  
  try{
    await waitForSomethingToBeTrue(() => globalBool);
    console.log("It was true");
   }
   catch(error){
    console.log(error);
   }
}

testIt();
<button id="clickme">Click me to make something true</button>

Upvotes: 1

trincot
trincot

Reputation: 350147

Here is an implementation that uses async await in combination with a promisified setTimeout (called delay):

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

const waitForSomethingToBeTrue = async (something, giveUp) => {
    let expired = () => true;
    delay(giveUp).then(() => something = expired);
    while (!something()) await delay(100);
    if (something === expired) throw "expired";
    return "success";
};

// Test whether button is pressed in time
buttonClicked = false;
document.querySelector("button").addEventListener("click", () => buttonClicked = true);

waitForSomethingToBeTrue(() => buttonClicked, 5000)
    .then(console.log)
    .catch(console.log);
<button>Make something true</button><br>
Press the button with 5 seconds to get success. If that delay expires this is reported.

Upvotes: 1

dave
dave

Reputation: 64657

You could just use a single setInterval, and clear it and resolve/reject a single promise:

const gen = function(t) {
    let value = false;
    setTimeout(() => value = true, t);
    return () => value;
};

const success = gen(2500);
const err = gen(3500);

const watch = (value) => {
    return new Promise((resolve, reject) => {
        const interval = setInterval(() => {
            if (value()) {
                resolve(true);
                clearInterval(interval);
            }
        }, 100);
        setTimeout(() => {
            clearInterval(interval);
            reject('did not complete in time');
        }, 3000)
    });
}

watch(success).then(console.log).catch(console.error);
watch(err).then(console.log).catch(console.error);

Upvotes: 1

epascarello
epascarello

Reputation: 207501

Make one promise, make a check inside that keeps checking the state.

function myFunction() {
  var x = Math.floor(Math.random() * 30);
  return x === 10;
}



function waitFor(method, max) {

  var start = Date.now();
  return new Promise((resolve, reject) => {

    function check() {
      var result = method();
      if (result) {
        resolve();
      } else if (Date.now() - start > max) {
        reject();
      } else {
        window.setTimeout(check, 100);
      }
    }
    
    check();

  });


}

waitFor(myFunction, 3000)
  .then(() => console.log('good'))
  .catch(() => console.log('bad'));

Upvotes: 2

Related Questions