Skip to content

Instantly share code, notes, and snippets.

@petsel
Last active February 6, 2024 10:34
Show Gist options
  • Save petsel/173fcf624059426e4c855b3276c72153 to your computer and use it in GitHub Desktop.
Save petsel/173fcf624059426e4c855b3276c72153 to your computer and use it in GitHub Desktop.
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