516

I'm playing with Node.js and Mongoose — trying to find specific comment in deep comments nesting with recursive function and forEach within. Is there a way to stop Node.js forEach? As I understand every forEach iteration is a function and and I can't just do break, only return but this won't stop forEach.

function recurs(comment) {
    comment.comments.forEach(function(elem) {

        recurs(elem);

        //if(...) break;

    });
}
Mike
  • 14,010
  • 29
  • 101
  • 161
kulebyashik
  • 5,359
  • 4
  • 18
  • 13

13 Answers13

1237

You can't break from a forEach. I can think of three ways to fake it, though.

1. The Ugly Way: pass a second argument to forEach to use as context, and store a boolean in there, then use an if. This looks awful.

2. The Controversial Way: surround the whole thing in a try-catch block and throw an exception when you want to break. This looks pretty bad and may affect performance, but can be encapsulated.

3. The Fun Way: use every().

['a', 'b', 'c'].every(function(element, index) {
  // Do your thing, then:
  if (you_want_to_break) return false
  else return true
})

You can use some() instead, if you'd rather return true to break.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
salezica
  • 74,081
  • 25
  • 105
  • 166
  • 91
    +1, though it sounds more natural to me to use `some()` and `return true` when you want to break. – Giacomo Jun 07 '11 at 07:54
  • 32
    Or more elegantly, put `return !you_want_to_break` inside the loop instead of the if..else block. Saves two lines. :-) – sffc Nov 27 '13 at 23:25
  • 24
    `every` supported everywhere except IE7 & 8 (I had to look it up, so thought I'd share) – jbobbins Sep 19 '14 at 00:55
  • 13
    There should be number 4. The Beautiful Way: use `some()` – Preexo Oct 21 '14 at 03:59
  • 1
    It should be the called 'misleading way'. The name suggests its the results not side effects you care about. – joozek Nov 27 '14 at 14:04
  • @jbobbins Yet another reason to drop IE8 support ;) – Megan Caithlyn Jul 05 '15 at 19:57
  • There's also a polyfill suggested by MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every#Polyfill. – Konstantin Grushetsky Jul 13 '15 at 08:50
  • 1
    Note also that since `some`/`every` returns `true` if any element does, you can go really crazy and nest this abuse arbitrarily deep.. – OJFord Aug 27 '15 at 14:48
  • 11
    Unfortunately using every() or some() will not solve cases where you would like to break on a specific value and return it. ECMAScript2015 new for...of (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) could help with that part but the drawback is that this solution can cause even more issues with older browsers. If you are willing to change route completely and use a different more generic approach, a solution like this (https://github.com/nbouvrette/forEach) could help you and potentially solve even other headaches. – Nicolas Bouvrette Jun 13 '16 at 00:14
  • Regarding #2, you say it "may have bad performance" - why? – noahnu Mar 10 '17 at 21:48
  • 1
    Can somebody explain the #1? How can it be done? Just interesting. – through.a.haze Mar 16 '17 at 11:20
  • 1
    IMPORTANT NOTE! `every` will only break with `== false` values whereas `some` will only break with `== true` values. Make sure you understand this concept before using this trick! – Jacksonkr Jul 21 '17 at 15:07
  • The 3rd way only end the every() loop. The following statements are still executed. – DarkMoon Aug 07 '17 at 01:42
  • @slezica, could you please expand on "the ugly way"? – Pyromonk Sep 10 '17 at 01:07
  • The second argument to `.forEach(callback, context)` is the `context` object, which can be accessed as `this` from within the callback. You could put an object there, with a property that tells you whether items should be processed or not. This still implies a linear walk down the array, unless you `throw`. – salezica Sep 10 '17 at 17:16
  • 3rd party libraries allows to use the same logic of `every`: if you return false, it will break the `each` Loop. In ES5 though, `every`is the way to go – Bernardo Dal Corno Mar 08 '18 at 18:28
  • How about maps? – Mathijs Segers Apr 12 '18 at 12:11
  • 1
    Or you can make run 'forEach' in replica of your array, and when you want to break just 'splice' the main array, but we can't use this way everywhere. why don't we use 'for' or 'while' if we want to break the loop. – Kanad Chourasia Aug 28 '19 at 14:08
  • I ran into the same dilemma. I just converted forEach to good old "for" loop and used break and be done with it. The best part is, I need not to worry whether it works in all popular browsers. – dotcoder Oct 06 '20 at 03:04
73

Breaking out of Array#forEach is not possible. (You can inspect the source code that implements it in Firefox on the linked page, to confirm this.)

Instead you should use a normal for loop:

function recurs(comment) {
    for (var i = 0; i < comment.comments.length; ++i) {
        var subComment = comment.comments[i];
        recurs(subComment);
        if (...) {
            break;
        }
    }
}

(or, if you want to be a little more clever about it and comment.comments[i] is always an object:)

function recurs(comment) {
    for (var i = 0, subComment; subComment = comment.comments[i]; ++i) {
        recurs(subComment);
        if (...) {
            break;
        }
    }
}
Domenic
  • 110,262
  • 41
  • 219
  • 271
56

forEach does not break on return, there are ugly solutions to get this work but I suggest not to use it, instead try to use Array.prototype.some or Array.prototype.every

var ar = [1,2,3,4,5];

ar.some(function(item,index){
  if(item == 3){
     return true;
  }
  console.log("item is :"+item+" index is : "+index);
});
Imal Hasaranga Perera
  • 9,683
  • 3
  • 51
  • 41
42

In some cases Array.some will probably fulfil the requirements.

rink.attendant.6
  • 44,500
  • 61
  • 101
  • 156
igor
  • 5,351
  • 2
  • 26
  • 22
  • This should be the canonical answer as it will actually stop processing once it finds the correct element. While `forEach` and `every` (as I understand it) can be hacked to return true on the first element it finds, it will still run through the entire array. Secondly, Javascript doesn't perform tail optimization and thus all recursive functions are inherently fragile until ES6 comes out. – Indolering Nov 20 '13 at 16:57
  • 1
    how is the browser support ? – Imal Hasaranga Perera Apr 01 '17 at 14:10
  • @imalhasarangaperera Here you can find current browser support. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some?v=example – JoannaFalkowska Apr 21 '17 at 12:15
29

As others have pointed out, you can't cancel a forEach loop, but here's my solution:

ary.forEach(function loop(){
    if(loop.stop){ return; }

    if(condition){ loop.stop = true; }
});

Of course this doesn't actually break the loop, it just prevents code execution on all the elements following the "break"

  • 1
    I like this one. I would just combine the last line to `loop.stop = condition` though. It shouldn't make any difference because when it's set to `true` it won't be run anymore. – pimvdb Jun 07 '11 at 08:55
  • 2
    Clever use of named function expressions – Raynos Jun 07 '11 at 09:26
  • 17
    I don't think this solution is a good idea. imagine you are looping 10000 elements and your condition is stop at second element, then you are going to do unnecessary iteration of 9998 times for nothing. The best approaches are either using `some` or `every`. – Ali Mar 07 '14 at 18:19
  • 1
    @Ali zyklus said that.... it's another, valid, solution! Depends on your case.... Why do people spend time on critisizing instead of presenting fresh new different solutions...?!?!? – Pedro Ferreira Mar 28 '16 at 10:36
  • 8
    @PedroFerreira my argument is valid. I'm not offending anybody. We need to be criticized about our code to make it better. I would rather read other implementation and help them improve it than reinvent the wheel. – Ali Mar 29 '16 at 19:35
  • @PedroFerreira The argument is valid for sure. However, in certain use cases when I know the number of elements isn't going to be very high, I would feel safe using it. It's good to have this as an option. :) – Jackson Holiday Wheeler Sep 13 '18 at 09:37
  • despite the early `return` condition, the `loop` function will still run for all elements in `ary`. – Mulan Dec 18 '22 at 16:15
25

I guess you want to use Array.prototype.find Find will break itself when it finds your specific value in the array.

var inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5}
];

function findCherries(fruit) { 
  return fruit.name === 'cherries';
}

console.log(inventory.find(findCherries)); 
// { name: 'cherries', quantity: 5 }
11

You can use Lodash's forEach function if you don't mind using 3rd party libraries.

Example:

var _ = require('lodash');

_.forEach(comments, function (comment) {
    do_something_with(comment);

    if (...) {
        return false;     // Exits the loop.
    }
})
exmaxx
  • 3,252
  • 28
  • 27
6
    var f = "how to stop Javascript forEach?".split(' ');
    f.forEach(function (a,b){
        console.info(b+1);
        if (a == 'stop') {
            console.warn("\tposition: \'stop\'["+(b+1)+"] \r\n\tall length: " + (f.length)); 
            f.length = 0; //<--!!!
        }
    });
4

Array.forEach cannot be broken and using try...catch or hacky methods such as Array.every or Array.some will only make your code harder to understand. There are only two solutions of this problem:

1) use a old for loop: this will be the most compatible solution but can be very hard to read when used often in large blocks of code:

var testArray = ['a', 'b', 'c'];
for (var key = 0; key < testArray.length; key++) {
    var value = testArray[key];
    console.log(key); // This is the key;
    console.log(value); // This is the value;
}

2) use the new ECMA6 (2015 specification) in cases where compatibility is not a problem. Note that even in 2016, only a few browsers and IDEs offer good support for this new specification. While this works for iterable objects (e.g. Arrays), if you want to use this on non-iterable objects, you will need to use the Object.entries method. This method is scarcely available as of June 18th 2016 and even Chrome requires a special flag to enable it: chrome://flags/#enable-javascript-harmony. For Arrays, you won't need all this but compatibility remains a problem:

var testArray = ['a', 'b', 'c'];
for (let [key, value] of testArray.entries()) {
    console.log(key); // This is the key;
    console.log(value); // This is the value;
}

3) A lot of people would agree that neither the first or second option are good candidates. Until option 2 becomes the new standard, most popular libraries such as AngularJS and jQuery offer their own loop methods which can be superior to anything available in JavaScript. Also for those who are not already using these big libraries and that are looking for lightweight options, solutions like this can be used and will almost be on par with ECMA6 while keeping compatibility with older browsers.

Nicolas Bouvrette
  • 4,295
  • 1
  • 39
  • 53
1

Below code will break the foreach loop once the condition is met, below is the sample example

    var array = [1,2,3,4,5];
    var newArray = array.slice(0,array.length);
    array.forEach(function(item,index){
        //your breaking condition goes here example checking for value 2
        if(item == 2){
            array.length = array.indexOf(item);
        }

    })
    array = newArray;
  • does it will not create a memory leakage problem? – Jasti Sri Radhe Shyam Jul 23 '18 at 21:00
  • definitely it will not create memory leakage, because we are setting the original array with same exact data as it is, and it will not be dangling pointer, if you have still concern about newArray variable which was used above, after reassigning array = newArray you can set newArray= null – D G ANNOJIRAO Jul 24 '18 at 15:59
-6

You can break from a forEach loop if you overwrite the Array method:

(function(){
    window.broken = false;

        Array.prototype.forEach = function(cb, thisArg) {
            var newCb = new Function("with({_break: function(){window.broken = true;}}){("+cb.replace(/break/g, "_break()")+"(arguments[0], arguments[1], arguments[2]));}");
            this.some(function(item, index, array){
                 newCb(item, index, array);
                 return window.broken;
            }, thisArg);
            window.broken = false;
        }

}())

example:

[1,2,3].forEach("function(x){\
    if (x == 2) break;\
    console.log(x)\
}")

Unfortunately with this solution you can't use normal break inside your callbacks, you must wrap invalid code in strings and native functions don't work directly (but you can work around that)

Happy breaking!

ApplePear
  • 5
  • 1
-12

Wy not use plain return?

function recurs(comment){
comment.comments.forEach(function(elem){
    recurs(elem);
    if(...) return;
});

it will return from 'recurs' function. I use it like this. Althougth this will not break from forEach but from whole function, in this simple example it might work

  • 1
    You can not return in a loop – Anonymoose Mar 12 '14 at 18:36
  • 1
    @Hazaart Although you can't do this in `forEach`, you can return in loops and the function will exit with no further iteration of the loop. (e.g. `for (... in ...)`, `while`, etc.) – mc9 Jul 09 '15 at 02:09
-17

jQuery provides an each() method, not forEach(). You can break out of each by returning false. forEach() is part of the ECMA-262 standard, and the only way to break out of that that I'm aware of is by throwing an exception.

function recurs(comment) {
  try {
    comment.comments.forEach(function(elem) {
      recurs(elem);
      if (...) throw "done";
    });
  } catch (e) { if (e != "done") throw e; }
}

Ugly, but does the job.

Joe Taylor
  • 638
  • 6
  • 12
  • 27
    -1 for spouting "use jquery" when the OP specifically said "node.js" –  Jun 07 '11 at 05:30
  • 1
    @cwolves I thought I saw "jQuery" in there somewhere. Guess not, but heh anyhow because jQuery and node.js can be used in conjunction with one another. – Joe Taylor Jun 07 '11 at 14:22
  • 6
    no, you can't use jQuery in node. If nothing else, there is an explicit reference to `window` which will throw an error if loaded in node. Then you have all of the wasted code: The entire Sizzle engine, the entire jQuery root function (literally -- there's a reference to `document` in it -- it ___expects___ a DOM), etc. The ONLY thing you can use from jQuery in node are a few of the helper functions, and those have clones in other libraries like underscore that are much better suited to node. –  Jun 07 '11 at 15:04
  • 2
    @cwolves Guess I'm getting rusty. [I was thinking of Rhino](http://ejohn.org/blog/bringing-the-browser-to-the-server/). – Joe Taylor Jun 08 '11 at 02:15