Reputation: 2616
I'm looking for a semantic way to perform some step on each item in a Javascript array while in the middle of a method chain, since I can't use forEach
for whatever reason:
forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable. The typical use case is to execute side effects at the end of a chain.
Laravel Collections have each
and tap
which perform largely the same function.
I can just use map
and return the original item at the end I suppose, but I was wondering if there was a more built-in, semantic method to do so.
Upvotes: 0
Views: 1036
Reputation: 7096
I can just use map and return the original item at the end I suppose, but I was wondering if there was a more built-in, semantic method to do so.
A built-in method that does what map
does, except that it returns the array unchanged without your code returning each element?
Or a built-in method like forEach
, except that it returns the array unchanged?
I don't think that exists.
While map
certainly does what you want once you return the item unchanged, you could wrap forEach()
in a chainable function that returns the array:
// add a chainable version of forEach() method to array
function addChainableForEach(array) {
const newArr = [...array];
Object.defineProperty(newArr, 'chainableForEach', {
value: function(callback) {
this.forEach(callback);
return this;
},
enumerable: false
});
return newArr;
}
const arr = [1, 2, 3];
const arr2 = addChainableForEach(arr)
.chainableForEach((el) => {
console.log(`el:`, el);
});
console.log(`arr2:`, JSON.stringify(arr2));
Upvotes: 0
Reputation: 8670
While this solution is far from perfect, you could simply filter the array and return true on each element.
This will allow you to keep the reference to each element in the array and perform an action for every single one of them.
array.someChainableFunction().filter(item => {
// we do something with each item
// then return true to keep all the items.
return true;
}).someOtherChainableFunction()...
const elements = [
{
value: 1
},
{
value: 2
},
{
value: 3
},
{
value: 4
},
{
value: 5
},
];
const output = elements.map(item => {
item.value++;
return item;
}).filter(item => {
// we do something with each items.
console.log('first map', item);
// and we return true since we don't want to actually filter the array.
return true;
}).map(item => {
item.value++;
return item;
}).forEach(item => {
console.log('second map', item);
});
console.log(output);
I would highly suggest wrapping this in another function, so it's easier to understand and does not confuse other developpers when using it througout your code.
Array.prototype.tap = function (callable) {
return this.filter(item => {
callable(item);
return true;
});
}
array.someChainableFunction().tap(item => {
// we do something with each item
}).someOtherChainableFunction()...
// here, we use an anonymous function rather than an arrow function
// because we need to have access to the `this` context of the Array object.
Array.prototype.tap = function (callable) {
return this.filter(item => {
callable(item);
return true;
});
}
const elements = [
{
value: 1
},
{
value: 2
},
{
value: 3
},
{
value: 4
},
{
value: 5
},
];
const output = elements.map(item => {
item.value++;
return item;
}).tap(item => {
// we use our custom function.
console.log('first map', item);
}).map(item => {
item.value++;
return item;
}).tap(item => {
console.log('second map', item);
});
console.log(output);
Upvotes: 1