How can I break (similar to the break
statement) from an implicit loop on an array?
The Array.prototype.map
, Array.prototype.forEach
, etc. functions imply a loop over the elements of the array. I want to conditionally break that loop early.
This contrived example:
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
colours.map(item => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
break;
}
});
How can I break (similar to the break
statement) from an implicit loop on an array?
The Array.prototype.map
, Array.prototype.forEach
, etc. functions imply a loop over the elements of the array. I want to conditionally break that loop early.
This contrived example:
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
colours.map(item => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
break;
}
});
causes a SyntaxError: unlabeled break must be inside loop or switch
.
How can I break the loop the same way a break
statement would?
-
1
Array#map
is not designed for iteration with side effects (and must not be used for that purpose), it cannot be stopped.Array#forEach
cannot be stopped either though. If you need to find something you need to useArray#indexOf
orArray#find
or any other lookup method. – zerkms Commented May 28, 2018 at 2:08 -
You can't break a map. You'll have to use a stoppable method, such as
for
orwhile
or, as @zerkms suggested, you could search for your item without iterating at all. – tao Commented May 28, 2018 at 2:09 -
You can't because those methods don't allow for it. But, if you really need that functionality, you should just use a
for
loop with a counter. – Scott Marcus Commented May 28, 2018 at 2:25
5 Answers
Reset to default 5You can't do it using a regular way. You can emulate the break
behaviour by remembering whether the loop is "broken". The lack of this solution is that the loop actually continues (though the iteration logic is skipped).
let isBroken = false;
colours.map(item => {
if (isBroken) {
return;
}
if (item.startsWith("y")) {
console.log("The yessiest colour!");
isBroken = true;
return;
}
});
The best solution for your example is to use a plain for
loop.
for (colour of colours) {
if (colour.startsWith("y")) {
console.log("The yessiest colour!");
break;
}
}
Also you can use a dirty way to actually stop a map
loop.
colours.map((item, index, array) => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
array.splice(0, index);
}
});
// The colours array will be modified after this loop
Although forEach
is designed to run some function that doesn't change the array (i.e. it is designed to do some other side effect for each item), it is explicitly documented to have no way of breaking the loop.
From the MDN documentation for forEach
:
There is no way to stop or break a
forEach()
loop other than by throwing an exception. If you need such behavior, theforEach()
method is the wrong tool.
So, despite forEach
being designed for side-effects, there is no normal access to the control structure of the loop.
Because Array.prototype.map
and Array.prototype.reduce
are intended to generate a new value, they are not designed for side effects like breaking early. The documentation doesn't appear to say that explicitly.
Possible alternatives to try: re-write the code to use Array.prototype.some
or Array.prototype.every
. These are explicitly documented to terminate the loop early when their condition is known (when some
would return true
, or when every
would return false
).
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
colours.some(item => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
return true;
}
});
Array#map
, Array#forEach
and so on have never been designed to be stopped. That would feel odd since the intent of map
as well forEach
really is to iterate over all items.
Also i don't think it is possible to notify the caller that a break
event has occurred, since it is within a function that is not an integral part of the original loop.
So let's see at a custom method that stops the loop at the first occurrence of true
without returning the matching value itself:
Object.defineProperty(Array.prototype, 'untilTrue', {
enumerable: false,
value: function(lambda) {
for(let i in this) {
if(lambda.call(this, this[i])) return;
}
}
});
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
colours.untilTrue(item => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
return true;
}
console.log(item);
});
Comparing this custom untilTrue
to the use of Array#find
:
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
colours.find(item => {
if (item.startsWith("y")) {
console.log("The yessiest colour!");
return true;
}
return false;
});
The only notable difference is that untilTrue
doesn't return the matching item - Array#find
does that in addition to call lambda
.
So in general i would just stick to Array#find
to keep the code neat and clean and use it like this:
const colours = ["red", "orange", "yellow", "green", "blue", "violet"];
if(colours.find(item => item.startsWith("y")) !== undefined) {
console.log("The yessiest colour!");
}
This stops the loop at the first match (and returns the matching element). Also note that you have to pare against undefined
- in case you were searching for a false
or null
value, the check would never evaluate to true
if just pared to true
.
you can throw an exception if your only option is to use Array.forEach
Refer this:
How to short circuit Array.forEach like calling break?
There are other methods available which can solve your purpose too. For example you can use method: Array.prototype.some() if you want to check some condition and break the loop based on that condition.
Example can be referenced here:
https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
you can create your custom forEach method in these ways
Array.prototype._forEach = function (callback) {
let _break = false;
const breaker = () => {
_break = true;
};
for (let index = 0; index < this.length; index++) {
if (_break) break;
callback(this[index], index, breaker);
}
};
// Example for usage:
const letters = ["a", "b", "c", "d", "e", "f", "g"];
letters._forEach((data, index, breaker) => {
if (data === "c") return; // continue role
if (data === "e") return breaker(); // break role
console.log(`log ${index}: ${data}`);
});
/**
* result:
* log 0: a
* log 1: b
* log 3: d
*/
or you can create top custom forEach method by creating
function forEach(items, callback) {
let _break = false;
const breaker = () => {
_break = true;
};
for (let index = 0; index < items.length; index++) {
if (_break) break;
callback(items[index], index, breaker);
}
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1741003473a4289794.html
评论列表(0条)