function loopWithDelayNonAsync({ doWhat, stopWhen, minInterval }) {
return new Promise((resolveWorkflow, rejectWorkflow) => {
let interval;
// a helper to transition to the next state,
// when the pending promise is fulfilled
const transition = ({ pending, next }) => {
// note: we need .catch() because next may throw,
// in which case we fail the whole workflow
pending.then(next).catch(rejectWorkflow);
}
// start with step1
step1();
// step1 will transition to step2 after completing a doWhat task
function step1() {
if (!stopWhen()) {
// start the interval timing here
interval = startInterval(minInterval);
// doWhat may or may not return a promise,
// thus we wrap its result with a promise
const pending = Promise.resolve(doWhat());
transition({ pending, next: step2 });
}
else {
// finish the whole workflow
console.log("finished.");
resolveWorkflow();
}
}
// step2 will transition to step3 after completing a delay
function step2() {
transition({ pending: interval(), next: step3 });
}
// step3 will transition to step1 after showing the time lapse
function step3(prevStepResults) {
// prevStepResults is what the pending promise
// from step2 has been resolved to
console.log(`resumed after ${prevStepResults}ms...`);
step1();
}
});
}
await loopWithDelayNonAsync({
doWhat: doSomethingForMs(150),
stopWhen: stopAfterMs(2000),
minInterval: 500
});
// a simple delay helper (in every single codebase :)
function delay(ms) {
return new Promise(r => setTimeout(r, ms)); }
// a higher-order helper to calculate a timelapse
function startTimeLapse() {
const startTime = Date.now();
return () => Date.now() - startTime;
}
// a higher-order helper for a minimal interval delay
function startInterval(ms) {
const sinceStarted = startTimeLapse();
return () => {
const sinceDelayed = startTimeLapse();
return delay(Math.max(ms - sinceStarted(), 0)).then(sinceDelayed);
};
}
// a higher-order helper to simulate an asynchronous task
function doSomethingForMs(ms) {
let count = 0;
return async () => {
const elapsed = startTimeLapse();
await delay(ms); // simulate an asynchronous task
console.log(`done something for the ${++count} time, it took ${elapsed()}ms`);
}
}
// a higher-order helper to tell when to stop
function stopAfterMs(ms) {
const elapsed = startTimeLapse();
return () => elapsed() > ms;
}