Underscore: _.chain & _.each are not playing together?

Created on 29 Dec 2013  ·  27Comments  ·  Source: jashkenas/underscore

Hi I'm not sure but I think this is a bug:

  _.chain([1,2,3,4]).each(function(current){ console.log(current) }).value(); // returns undefined
  //where: 
  _.chain([1,2,3,4]).map(function(current){ return current; }).value() // returns expected. 

I guess chained function have to return values but then tap is not an iterable, im looking for a way to access the values without un-chaining them and without mutation.

question

Most helpful comment

Is there now an alternative method that can be chained and allow modifying elements of an array in place? I was searching for something like a peek:

_.chain(myArray)
    ...
    .peek(e -> e.craftedVar = e.otherVar * 2)
    ...
    .value();

All 27 comments

That's correct. Unlike map, each doesn't have a return value, so calling value on it does nothing. If you need to pass a mutated array through the chain, it's best to stick with map.

ok so map, how about _.chain().tap(function(values) { values.forEach(function(current) {}; });
does not seem elegant but seems like its logical...

Yep, looks good by me:

_.chain([1,2,3,4])
 .tap(_.bind(console.log, console))
 .map(function(val) { return val * 2 })
 .value(); // [2,4,6,8]

It would be nice to mention that each doesn't return a value in the documentation.

@xixixao each follows the semantics of Array#forEach, which also doesnt return any value. If you need something returned just use a map.

I don't see anything preventing Underscore from making _.each chainable. It breaks with native semantics for other methods. There are subtle differences with suggesting _.map like it doesn't return the same array and it requires a return value in the callback.

Why would each need to be chainable? You're not mutating the array in any way (the way you are with something like a map or filter), so therefore there is no no expectations of the value being reusable.

Why would each need to be chainable?

Why not allow chaining, what could it hurt? It's handy for cases like _(collection).each(intermediateActions).filter(...).

I would make the method undefined in a wrapped object context.

@dw40 but what are you doing in those intermediate actions that can't better be handled with a map?

but what are you doing in those intermediate actions that can't better be handled with a map?

It doesn't really matter what the intermediate step is, it could be a number of things, the benefit of _.each chaining is that the intermediate callback doesn't have to return a value and it doesn't need to create a new array.

@dw40 want to whip up a quick pull? You're talking about special-casing each as part of the mix-in right?

@akre54 I know that each follows forEach, I literally meant that it would be good to mention the behavior in the documentation.

That said, I agree that each should just return the original collection for chaining (always, whatever syntax is each being called with).

You're talking about special-casing each as part of the mix-in right?

Simpler than that. Just have_.each return obj. No need to special case.

But then you're breaking compat with the spec. That's not what each does.

But then you're breaking compat with the spec. That's not what each does.

I previously wrote:

I don't see anything preventing Underscore from making _.each chainable. It breaks with native semantics for other methods.

I don't see that as a big deal. Underscore already parts with the spec'ed behavior for better dev usability in several methods. For example in edge _.keys(...) doesn't throw if the argument passed is not an object, and methods like _.every and _.some work if you don't pass an iterator.

I don't think it's a huge change, but it's definitely unexpected that each will return anything, let alone the value it was passed (and a much bigger spec break than checking for nullish arguments).

To be clear, you're talking about making this change, right?

diff --git a/underscore.js b/underscore.js
index 7a30b0a..2b56536 100644
--- a/underscore.js
+++ b/underscore.js
@@ -74,7 +74,7 @@
   // Handles objects with the built-in `forEach`, arrays, and raw objects.
   // Delegates to **ECMAScript 5**'s native `forEach` if available.
   var each = _.each = _.forEach = function(obj, iterator, context) {
-    if (obj == null) return;
+    if (obj == null) return obj;
     if (nativeForEach && obj.forEach === nativeForEach) {
       obj.forEach(iterator, context);
     } else if (obj.length === +obj.length) {
@@ -87,6 +87,7 @@
         if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
       }
     }
+    return obj;
   };

To be clear, you're talking about making this change, right?

Right.

I dont particularly care one way or another but it would be good to hear from more people about real use cases for or against this before merging.

I think you can go for it. Would be a little more useful.

In my opinion at least when working in a chained context, making _.each return a value is expected behavior, so +1 :)

@LeonFedotov @dw40 give that a try.

@akre54 Awesome.

@akre54 great, thanks!

Interestingly, we've reverted to the pre-1.1.3 behaviour.

The following addition to the 1.1.3 release notes was made in 3b916a2cf788a4588f08323c39786b6fb5ddbca6:

_.each no longer returns the iterated collection, for improved consistency with ECMA5's forEach.

Ha. I knew it seemed familiar. Good spot.
On Feb 1, 2014 11:23 PM, "David Chambers" [email protected] wrote:

Interestingly, we've reverted to the pre-1.1.3 behaviour.

The following addition to the 1.1.3 release notes was made in 3b916a2https://github.com/jashkenas/underscore/commit/3b916a2cf788a4588f08323c39786b6fb5ddbca6
:

_.each no longer returns the iterated collection, for improved
consistency with ECMA5's forEach.

Reply to this email directly or view it on GitHubhttps://github.com/jashkenas/underscore/issues/1391#issuecomment-33883825
.

Is there now an alternative method that can be chained and allow modifying elements of an array in place? I was searching for something like a peek:

_.chain(myArray)
    ...
    .peek(e -> e.craftedVar = e.otherVar * 2)
    ...
    .value();
Was this page helpful?
0 / 5 - 0 ratings

Related issues

chikamichi picture chikamichi  ·  8Comments

jezen picture jezen  ·  8Comments

jdalton picture jdalton  ·  4Comments

zackschuster picture zackschuster  ·  5Comments

danilopolani picture danilopolani  ·  5Comments