javascriptcontrol-structure

Break the loop of an Array looping function (map, forEach, etc.)


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?


Solution

  • 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 compare against undefined - in case you were searching for a false or null value, the check would never evaluate to true if just compared to true.