I hope this is a simple question but I currently can't wrap my head around.
What I want to do is break out of a while loop which contains a delay when a promise gets resolved.
In pseudocode this would look like:
while( ! promise.resolved ){
doSomthing()
await sleep( 5min )
}
The loop must break instantly after the promise is resolved and not wait for sleep to finish.
sleep is currently implemented trivially by setTimeout but can be implemented differently.
I would like to have some kind of spatial separation between the awaited promise and sleep to show its working more clearly*) (and because I hope for an elegant solution to learn from). So what would work but I don't like is something like:
while( true ){
doSomething()
try {
await Promise.race([promise,rejectAfter(5000)])
break
} catch( e ){}
}
If you must know:
doSomething is sending out status information.
promise is waiting for user interaction.
*) Part of the purpose of this code is to show/demonstrate others how things are expect to work. So I'm looking for the clearest solution on this level of implementation.
CodePudding user response:
Minor modification of your idea to make it work better:
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
async function waitAndDo(promise) {
let resolved = false;
const waitFor = promise
.then((result) => resolved = true);
while(!resolved) {
doSomething();
await Promise.race([waitFor, sleep(5000)]);
}
}
- The function accepts a promise and will be working until it resolves.
- The
waitForpromise will finish afterpromiseis fulfilled andresolvedupdated totrue. - The
whileloop can then loop until theresolvedvariable is set totrue. In that case, the loop will end an execution continues after it. - Inside the loop,
Promise.race()will ensure that it will stop awaiting as soon as the promise resolves or the sleep expires. Whichever comes first.
Therefore, as soon as the promise gets resolve, the .then() handler triggers first and updates resolved. The await Promise.race(); will end the waiting after and the while loop will not execute again, since resolved is now true.
CodePudding user response:
One approach would be to wait for the promise result, assuming it is a truthy value1:
const promise = someTask();
let result = undefined;
while (!result) {
doSomething();
result = await Promise.race([
promise, // resolves to an object
sleep(5000), // resolves to undefined
]);
}
1: and if it isn't, either chain .then(_ => true) to the promise, or make sleep fulfill with a special value that you can distinguish from everything someTask might return (like the symbol in Jeff's answer).
Also works nicely with a do-while loop, given result is always undefined at first:
const promise = someTask();
let result = undefined;
do {
doSomething();
result = await Promise.race([
promise, // resolves to an object
sleep(5000), // resolves to undefined
]);
} while (!result);
A downside here is that if doSomething() throws an exception, the promise is never awaited and might cause an unhandled rejection crash when the task errors.
Another approach would be not to use a loop, but an old-school interval:
const i = setInterval(doSomething, 5000);
let result;
try {
result = await someTask();
} finally {
clearInterval(i);
}
A downside here is that doSomething is not called immediately but only after 5s for the first time. Also, if doSomething throws an exception, it will instantly crash the application. It still might be a good approach if you don't expect doSomething to throw (or handle each exception in the setInterval callback and expect the "loop" to carry on).
The "proper" approach that will forward all exceptions from both someTask() and doSomething() could look like this:
let done = false;
const result = await Promise.race([
(async() => {
while (!done) {
doSomething();
await sleep(5000)
}
})(),
someTask().finally(() => {
done = true;
}),
]);
(Instead of .finally(), you can also wrap the Promise.race in a try-finally, like in approach two.)
The only little disadvantage in comparison to approach two is that sleep(5000) will keep running and is not immediately cancelled when someTask finishes (even though result is immediately available), which might prevent your program from exiting as soon as you want.
CodePudding user response:
Seems like you would just use some sort of interval and kill it when the promise is done.
const updateMessage = (fnc, ms, runInit) => {
if (runInit) fnc();
const timer = window.setInterval(fnc, ms);
return function () {
console.log('killed');
timer && window.clearTimeout(timer);
}
}
const updateTime = () => {
document.getElementById("out").textContent = Date.now();
}
const updateEnd = updateMessage(updateTime, 100, true);
new Promise((resolve) => {
window.setTimeout(resolve, Math.floor(Math.random()*5000));
}).then(updateEnd);
<div id="out"></div>
CodePudding user response:
As an alternative to VLAZ's very reasonable answer, you can avoid the separate boolean sentinel by having your sleep function return some kind of unique sentinel return value that indicates the the timeout. Symbol is exactly the kind of lightweight, unique object for this use case.
function sleepPromise(ms, resolveWith) {
return new Promise(resolve => {
setTimeout(resolve, ms, resolveWith);
});
}
const inputPromise = new Promise(
resolve => document.getElementById("wakeUp").addEventListener("click", resolve));
async function yourFunction() {
const keepSleeping = Symbol("keep sleeping");
do {
/* loop starts here */
console.log("Sleeping...");
/* loop ends here */
} while (await Promise.race([inputPromise, sleepPromise(3000, keepSleeping)]) === keepSleeping);
console.log("Awake!");
}
yourFunction();
<button id="wakeUp">Wake up</button>
