Erich
Erich

Reputation: 2616

*Chainable* Javascript method to do something for each item of an array

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

Answers (2)

terrymorse
terrymorse

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

Nicolas
Nicolas

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

Related Questions