Breaking out of a forEach loop

An email going around the client-side mailing list at Schematic casually mentioned forEach loops. The sender liked them, but tended to avoid them because there was no way to break out of one.

Naturally, this sent me on the hunt to see if it was at all possible. A furious Google search led me to a comment by Dean Edwards (who else?). He suggested throwing yourself out of the loop and wrapping the throw in a try catch statement to silence the throw. Like so:

var array = [1, 2, 0, 3, 4, 5];
try {
  array.forEach(function(item) {
    if (item === 0) {
      throw new Error(); // Simulated break
    }
    console.log(item);
  });
} catch (e) {}

That does the trick. Fellow developer/comrade Lenny Burdette pointed out that this was exactly the method Prototype uses to break their own custom loops.

But of course, there’s caveats. With Prototype, you merely have to call $break to have it automate this for you. With Flow, you’re exposed to the elements and need to handle it yourself. It’s a neat trick, but quite honestly it’s not very user friendly, and looks a bit unwieldy as a workaround.

I browsed the forEach documentation and went through possible solutions. One was to extend forEach and include a custom break function in it. But I decided against this for a few reasons. One, it would violate the rules (#3, to be exact). Over-extending a native function would bring more hurt than it would good. Second, Safari 2 has a problem with extending forEach, so it wasn’t a viable solution either.

But I had a hunch. What if I simulated a break by splicing off the remaining objects in the array, thereby terminating the loop entirely? I gave it a try:

var array = [1, 2, 0, 3, 4, 5];
array.forEach(function(item, i) {
  if (item === 0) {
    array.splice(i, array.length - i); // Simulated break
  } else {
    console.log(item);
  }
});

Success! The loop terminates, because there are no more objects to continue to. There’s just one problem: there are no more objects to continue to. It mutates the array, so it’s not reliable if you need to reuse the array. You’re limited to using it only once, or having to rebuild the array.

Ah, but what if you splice the array, and immediately concatenate it? Would it have the same effect and stop the array in its tracks?

var array = [1, 2, 0, 3, 4, 5];
array.forEach(function(item, i) {
  if (item === 0) {
    array = array.concat(array.splice(i, array.length - i)); // F-f-f-forEach breaker!
  } else {
    console.log(item);
  }
});

Double success! In this example, the array is spliced from the current index point, and all remaining objects are dropped off, ending the loop. The magic happens when we rejoin the array using concat, and reassign the newly constructed object to the array variable. The array is mutated back to its original form, and you’ve broken the forEach loop.

Still, it’s not as satisfyingly simple as a break, is it? Well, we can’t call a break, but we can get close. The next Flow revision will contain the exit Array extra, which will provide you with a keyword (or, as close enough a keyword as I could get) to break a forEach loop:

var array = [1, 2, 0, 3, 4, 5];
array.forEach(function(item, i) {
  if (item === 0) {
    array = array.exit(i);
  } else {
    console.log(item);
  }
});

del.icio.us:Breaking out of a forEach loopdigg:Breaking out of a forEach loop

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*