Breaking Bad (Loops in JavaScript Libraries)

JavaScript Libraries are important (e.g., jQuery, Lo-Dash, Underscore), but in the case of asynchronous loops (`forEach` and `each`) they create a lot of confusion… Lo-Dash is not equal to Underscore, unless a special underscore-compatible version is being used.

It was sort of a surprise for me when I discovered inconsistencies in the most popular JavaScript libraries in how they handle their each and forEach loops. This post compares:

  • forEach Loop in Native JavaScript
  • each Loop in Lo-Dash
  • each Loop in jQuery
  • each Loop in Underscore.js
  • forEach Loop in Underscore.js

forEach Loop in Native JavaScript

JavaScript Libraries are important (e.g., jQuery, Lo-Dash, Underscore), but in the case of functional loops (forEach and each) they create a lot of confusion (for loop can be broken with ‘break’). Let’s inspect the example of native JavaScript code for the forEach method:

[1,2].forEach(function(v){
  alert(v);
  return false;
})

This will display us two alert boxes. Try the native JavaScript code yourself in JSFiddle.

This is an expected behavior in most cases because with each iteration we invoke a new function. Unlike the for (var i=0; i<arr.length; i++) {} code that has no function/iterators.

However, in Lo-Dash and jQuery similar code breaks the loops!

Breaking each Loop in Lo-Dash

The Lo-Dash code with each produces only one alert:

_.each([1,2],function(v){
  alert(v);
  return false;
})

Try the above Lo-Dash code yourself in JSFiddle.

Breaking each Loop in jQuery

Likewise, the jQuery each example shows only the first alert:

$.each([1,2],function(i, v){
  alert(v);
  return false;
})

Try the jQuery code yourself in JSFiddle.

Non-Breaking each Loop in Underscore.js

To complicate the matter, Underscore.js and Backbone.js remain true to the native JavaScript interpretation.

The Underscore.js each example that iterates through the each item and doesn’t break:

_.each([1,2],function(v){
  alert(v);
  return false;
})

Try the Underscore each method in JSFiddle.

Non-Breaking forEach Loop in Underscore.js

Just for the sake of it, the Underscore forEach() was tested. It reliably produced the results similar to the native forEach(): two alerts!

The Underscore forEach() code:

_.forEach([1,2],function(i, v){
  alert(v);
  return false;
})

Try the Underscrore.js forEach() code yourself at JSFiddle.

The Code-Breaking Difference Between Lo-Dash and Underscore

The conclusion of this brief post is that Lo-Dash is not equal to Underscore, unless a special underscore-compatible version is being used. This was kindly pointed out to me by John-David Dalton (@jdalton):

PS: Underscore.js forEach is more browser compatible than native forEach because the latter was a later addition to the JavaScript API and is not supported by older browsers.

Author: Azat

Techies, entrepreneur, 20+ years in tech/IT/software/web development expert: NodeJS, JavaScript, MongoDB, Ruby on Rails, PHP, SQL, HTML, CSS. 500 Startups (batch Fall 2011) alumnus. http://azat.co http://github.com/azat-co

18 thoughts on “Breaking Bad (Loops in JavaScript Libraries)”

  1. Hi Matt, yes, it’s a good point. In the old times, jQuery had to re-invent the standard. That’s another good thing why devs should use jQuery or a similar browser — some fancy new ECMAS APIs might not be available on old browsers (of course if devs had to support old browsers).

  2. As you wrote, “The conclusion of this brief post is that Lo-Dash is not equal to Underscore,” so jQuery is not really the focus, but I imagine the discrepancy between `jQuery.each` and `Array.prototype.forEach` has something to do with the fact that the former was implemented long before the latter was introduced as standard JavaScript.

  3. You have mistake in Underscore forEach example. The first parameter is v(value), and second one is i(index). This is also create dissonance to all who start use Underscore after some experience with jQuery, where index goes first.
    By the way forEach is just alias to each in Underscore http://underscorejs.org/#each

    Also you say nothing about context, I mean this pointer. In jQuery this === v, in Underscore by default this === global object, but you can specify it by putting third parameter to each/forEach function. This is also make a sense.

  4. I have noticed some weirdness between JQuery and underscore before, however, I could never tell what they were. Excellent post. Simple, basic and straight to the point :)

  5. Every() is useful method to know about, but the point of this article to bring awareness about inconsistent behavior among what seemed to be analogous methods.

  6. You don’t really need libs to do breaking loops. There’s Array.prototype.every() that works in same way all those lib functions do. Code like this: [1,2].every(function(v){alert(v);return false;}) will works in modern browsers AND will break after 1st alert.

  7. A good way to stop native forEach iteration could be to throw StopIteration :

    window.StopIteration = window.StopIteration || {};
    try {
    [1,2,3].forEach(function(item) {
    console.log(item);
    throw StopIteration;
    });
    }
    catch(ex) {
    // If the exception is StopIteration, ignore it and continue the exception normally.
    if (ex !== StopIteration)
    throw ex;
    }

  8. Yesterday the post was ending with “code that has no function/iterators” and looked like spam – which is probably the reason it got deleted from NodeJS Italy group.

    Maybe it was just a temporary error with your blog

  9. Maybe you should re-read the post then… the point is to compare forEach/each in different libraries, not to compare for and forEach which are completely different things.

  10. Michal, sure we can do a lot of things, but that’s not the point of this post. ;-) The point of this post is to illustrate the breakage of the each/forEach loop in different libraries.

  11. And what’s more, you can shorten the code by passing a function as a parameter: [1,2].forEach(alert)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.