Reputation: 1552
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:
how can I solve this problem without having the above issues?
Upvotes: 1
Views: 1267
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
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
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
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