admin管理员组文章数量:1406063
What is the best way to implement a simulation of a loop's break
statement, when one does iterate through a user/engine defined function?
forEach([0, 1, 2, 3, 4], function (n) {
console.log(n);
if (n === 2) {
break;
}
});
I've thought of implementing forEach
in a way that would break when the function returns false
. But I would like to hear thoughts on how that is normally done.
What is the best way to implement a simulation of a loop's break
statement, when one does iterate through a user/engine defined function?
forEach([0, 1, 2, 3, 4], function (n) {
console.log(n);
if (n === 2) {
break;
}
});
I've thought of implementing forEach
in a way that would break when the function returns false
. But I would like to hear thoughts on how that is normally done.
2 Answers
Reset to default 7return
ing false
is the most mon way to do it. That's what jQuery's iterator function .each()
does:
We can break the $.each() loop at a particular iteration by making the callback function return false. Returning non-false is the same as a continue statement in a for loop; it will skip immediately to the next iteration.
And its very simplified implementation:
each: function( object, callback ) {
var i = 0, length = object.length,
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false; // break if false is returned by the callback
value = object[++i] ) {}
return object;
}
Since the OP explicitly did ask for "simulating ... break
... inside ... forEach
", and since the language core now has much more features than 11½ years ago, one actually could implement quite easily a prototypal array method which not only enables a break
but also a continue
mand, similar to both statements break
and continue
.
In order to achieve the implementation of the iterating array method, one first needs to write some abstractions which do borrow the basic idea from AbortController
and its related AbortSignal
.
Thus, one would implement e.g. a PausedStateSignal
...
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;
}
}
... which is going to be used by its PauseController
...
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;
}
}
... where PausedStateSignal
has to extend EventTarget
in order to be able of signaling state-changes via dispatchEvent
, and where PauseController
features the two main methods break
and continue
.
Both implementations are relying on class syntax, private properties, get
syntax and a private, protected state
object which gets shared by reference in between a controller and a signal instance. The latter gets achieved by a connect
ing callback function which is passed at a signal's instantiation time.
Having covered that part, one can continue with the actual implementation of an array method which, in addition of the standard forEach
functionality, is capable of three things ...
- allowing to pause/halt the callback function's execution via
break
, - and via
continue
allowing ...- either to continue a paused/halted loop,
- or to skip the loop's next iteration step.
The implementation could be named e.g. forEachAsyncBreakAndContinue
; it does make use of the above described signal and controller abstractions, might look like follows ...
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-mand before.
continueLooping();
} else {
// - continue-mand while already running which
// is equal to skipping the next occurring cycle.
++idx;
}
});
continueLooping();
return promise;
}
... and finally gets assigned for demonstration purposes via Reflect.defineProperty
as forEachAsyncBC
to Array.prototype
...
Reflect.defineProperty(Array.prototype, 'forEachAsyncBC', {
value: forEachAsyncBreakAndContinue,
});
The now prototypal forEachAsyncBC
method is always going to return a promise. This Promise
instance either reject
s or resolve
s; the former in case the provided callback function does raise an error at any time it gets invoked, and the latter in case the iteration cycle has been fully pleted.
Thanks to all the abstractions an executable example code which does test all of the mentioned features can be written as easy as that ...
(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 });
})();
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
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-mand before.
continueLooping();
} else {
// - continue-mand while already running which
// is equal to skipping the next occurring cycle.
++idx;
}
});
continueLooping();
return promise;
}
Reflect.defineProperty(Array.prototype, 'forEachAsyncBC', {
value: forEachAsyncBreakAndContinue,
});
</script>
本文标签:
版权声明:本文标题:javascript - How to simulate a loop's 'break' statement inside an array-iterating, custom implemented, & 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744957630a2634453.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论