Last active
February 6, 2024 10:34
-
-
Save petsel/173fcf624059426e4c855b3276c72153 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PausedStateSignal extends EventTarget { | |
// shared protected/private state. | |
#state; | |
constructor(connect) { | |
super(); | |
this.#state = { | |
isPaused: false, | |
}; | |
connect(this, this.#state); | |
} | |
get paused() { | |
return this.#state.isPaused; | |
} | |
} | |
class PauseController { | |
#signal; | |
#signalState; | |
constructor() { | |
new PausedStateSignal((signal, signalState) => { | |
this.#signal = signal; | |
this.#signalState = signalState; | |
}); | |
this.#signalState.isPaused = false; | |
} | |
get signal() { | |
return this.#signal; | |
} | |
break() { | |
const isPaused = this.#signalState.isPaused; | |
if (!isPaused) { | |
this.#signalState.isPaused = true; | |
} | |
this.#signal.dispatchEvent( | |
new CustomEvent('break', { detail: { pausedBefore: isPaused } }) | |
); | |
return !isPaused; | |
} | |
continue() { | |
const isPaused = this.#signalState.isPaused; | |
if (isPaused) { | |
this.#signalState.isPaused = false; | |
} | |
this.#signal.dispatchEvent( | |
new CustomEvent('continue', { detail: { pausedBefore: isPaused } }) | |
); | |
return isPaused; | |
} | |
} | |
// - asynchronously implemented `forEach` array method which | |
// provides a `PauseController` instance as 4th parameter | |
// to its callback function, where the latter's two methods | |
// `break` and `continue` enable the following ... | |
// | |
// - pause a `forEach` loop by invoking `break`. | |
// - by invoking `continue` ... | |
// - either continuing a paused `forEach` loop. | |
// - or skipping the `forEach` loop's next iteration step. | |
// | |
function forEachAsyncBreakAndContinue(callback, context = null) { | |
const { promise, reject, resolve } = Promise.withResolvers(); | |
const controller = new PauseController; | |
const { signal } = controller; | |
const arr = this; | |
const { length } = arr; | |
let idx = -1; | |
function continueLooping() { | |
while(++idx < length) { | |
if (signal.paused) { | |
--idx; | |
break; | |
} | |
try { | |
callback.call(context, arr.at(idx), idx, arr, controller); | |
} catch (exception) { | |
reject(exception.message ?? String(exception)); | |
} | |
} | |
if (idx >= length) { | |
resolve({ success: true }); | |
} | |
} | |
signal.addEventListener('continue', ({ detail: { pausedBefore } }) => { | |
if (pausedBefore) { | |
// - continue after already having | |
// encountered a break-command before. | |
continueLooping(); | |
} else { | |
// - continue-command while already running which | |
// is equal to skipping the next occurring cycle. | |
++idx; | |
} | |
}); | |
continueLooping(); | |
return promise; | |
} | |
Reflect.defineProperty(Array.prototype, 'forEachAsyncBC', { | |
value: forEachAsyncBreakAndContinue, | |
}); | |
(async () => { | |
const result = await [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] | |
.forEachAsyncBC((value, idx, arr, controller) => { | |
console.log({ value, idx }); | |
if (value === 9 || value === 3) { | |
console.log(`... skip over next value => ${ arr[idx + 1] } ...`); | |
// skip over. | |
controller.continue(); | |
} else if (value === 4 || value === 6) { | |
console.log(`... break at value ${ value } ... continue after 5 seconds ...`); | |
setTimeout(controller.continue.bind(controller), 5000); | |
// break loop. | |
controller.break(); | |
} | |
}); | |
console.log({ result }); | |
})(); | |
/* | |
const controller = new BreakAndContinueController; | |
const { signal } = controller; | |
console.log({ controller, signal }); | |
function handleBreakAndContinue(evt) { | |
// console.log('handleBreakAndContinue ...', { evt }); | |
console.log({ | |
currentTarget: evt.currentTarget, | |
"currentTarget.paused": evt.currentTarget.paused, | |
}); | |
} | |
signal.addEventListener('break', handleBreakAndContinue); | |
signal.addEventListener('continue', handleBreakAndContinue); | |
controller.break(); | |
controller.continue(); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment