Javascript Polyfills: forEach, map, filter and reduce
Tags: javascript, webdev, programming, tutorial
Published: 2021-10-23 10:10:29

The Javascript language has been evolving steadily over the years. New features on the language appear regularly. Some older browsers may not support these modern functionalities.
 
A polyfill is a piece of code that implements a feature on browsers that do not support the feature. Hence, the name - it *fills* the gap by adding missing implementations.

Writing polyfills is commonly asked during interviews. This article focuses on the same for the popular Javascript array methods - forEach, map, reduce and filter, while taking care of the edge cases.

For details on the usage and syntax of these array methods refer [MDN | Array instance methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods).

## .forEach( )

The `forEach` method invokes the provided callback function for each element in the array.

### Syntax

```javascript
forEach(function callbackFn(element, index, array) { ... }, thisArg);

```

Some important things to note about `forEach`:

- `callbackFn` is called on each element of the array. 
- `forEach` returns `undefined`.
- `callbackFn` must be called in the context of `thisArg`. If `thisArg` is not passed, `callbackFn` is invoked as a regular function.
- If a callback function is not passed as the first argument, `forEach` throws a `TypeError`.
- If `forEach` is invoked on `null` or `undefined`, it throws a `TypeError`

Let's begin with the first step:

```javascript
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = 
function (callbackFn, thisArg) {
    for (let i = 0; i < this.length; i++) {
      callbackFn(this[i], i, this);
    }
  };
}

```  
We first check if the function is already available in the prototype chain of `Array`. `this` inside the function references the array on which `forEach` is called. 

`forEach` also accepts an optional second argument - `thisArg`. If passed, the callback function must be invoked in the context of `thisArg` i.e. `this` inside `callbackFn` must be set to `thisArg`. This can be done using the [call() method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call):

```javascript
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = 
function (callbackFn, thisArg) {
    for (let i = 0; i < this.length; i++) {
      callbackFn.call(thisArg, this[i], i, this);
    }
  };
}

```

Time to handle the error cases! 
- What if a callback function is not passed to `forEach`? 
- What if `forEach` is not invoked on an array? 

In the above cases, an Error object must be thrown along with a descriptive message. Here, we will replicate the behavior shown by the original `forEach` method.

```javascript
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = function (callbackFn, thisArg) {
    if (this == null || this === window)
      throw TypeError('Array.prototype.myForEach called on null or undefined');

    if (typeof callbackFn !== 'function')
      throw TypeError(`${callbackFn} is not a function`);

    for (let i = 0; i < this.length; i++) {
      callbackFn.call(thisArg, this[i], i, this);
    }
  };
}

```

`this == null || this === window` - This condition is true if forEach is invoked as a standalone function (i.e not a method invocation). For example:

```javascript
const myUnboundForEach = Array.prototype.myForEach;

myUnboundForEach();

```
`myUnboundForEach()` is executed like a normal function expression. `this` inside the callback function will be the `global` object (window) in non-strict mode or `undefined` in the strict mode. Both these conditions are handled above. In both cases the `TypeError` is thrown.

And that's it! We have created our own implementation of the JS array method `forEach` and have also handled the error conditions.

The polyfill implementation for the rest of the methods are very similar and only differ in the core functionality of the method.

## .map( )

The `map` method creates an array that contains values returned by the callback function invoked on each element in the calling array. Our function should now return the newly created array.

### Syntax

```javascript
map(function callbackFn(element, index, array) { ... }, thisArg);

```

### Polyfill

```javascript
if (!Array.prototype.myMap) {
  Array.prototype.myMap = function (callback, thisArg) {
    if (this == null || this === window)
      throw TypeError('Array.prototype.myMap called on null or undefined');

    if (typeof callback !== 'function')
      throw TypeError(`${callback} is not a function`);

    const mappedArray = [];
    for (let i = 0; i < this.length; i++) {
      const mappedValue = callback.call(thisArg, this[i], i, this);
      mappedArray[i] = mappedValue;
    }
    return mappedArray;
  };
}

```

## .filter( )

The `filter` method creates an array that contains only those elements of the calling array that pass the test provided by the callback function.

### Syntax

```javascript
filter(function callbackFn(element, index, array) { ... }, thisArg);

```

### Polyfill

```javascript
if (!Array.prototype.myFilter) {
  Array.prototype.myFilter = function (callback, thisArg) {
    if (this == null || this === window)
      throw TypeError(
        'Array.prototype.myFilter is called on null or undefined'
      );

    if (typeof callback !== 'function')
      throw TypeError(`${callback} is not a function`);

    const filtered = [];

    for (let i = 0; i < this.length; i++) {
      if (callback.call(thisArg, this[i], i, this)) filtered.push(this[i]);
    }

    return filtered;
  };
}

```

## .reduce( )

The `reduce` method works a little differently than the above methods. It accepts a *reducer* callback function that is called on each element of the array along with the returned value from the previous invocation. After calling the reducer across all array elements, the single, accumulated result is returned.

### Syntax

```javascript
reduce(function callbackFn(previousValue, currentValue, currentIndex, array) { ... }, initialValue);

```

Some important things to note about `reduce`:

1. The second argument to `reduce` is an optional `initialValue`, used to initialize `previousValue`.
2. Value returned from `callbackFn` after traversing all elements of the array is ultimately returned from `reduce`.
3. If `initialValue` is not provided, `previousValue` is initialized to the first element in the array, and `reduce` begins traversal from the second element in the array.
4. If array is empty and `initialValue` is not provided, a `TypeError` is thrown.

Lets' begin with the main working of `reduce`:

```javascript
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function (callback, initialValue) {
    let previousValue = initialValue;
    let startIndex = 0;

    if (initialValue == null) {
      previousValue = this[0];
      startIndex = 1;
    }

    for (let index = startIndex; index < this.length; index++) {
      previousValue = callback(previousValue, this[index], index, this);
    }

    return previousValue;
  };
}

```

This covers points 1, 2 and 3 above. 

Time to handle the error cases:

- What if `initialValue` is not provided and array is empty?
In this case, `previousValue` will be assigned `undefined`. We can check for this and throw a `TypeError` with the appropriate error message.

- Is a callback function passed?
- Is `reduce` called on `null`/`undefined`?

All the above error cases are handled as follows:

```javascript
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function (callback, initialValue) {
    if (this == null || this === window)
      throw TypeError('Array.prototype.myReduce called on null or undefined');

    if (typeof callback !== 'function')
      throw TypeError(`${callback} is not a function`);

    let previousValue = initialValue;
    let startIndex = 0;

    if (initialValue == null) {
      previousValue = this[0];
      startIndex = 1;
    }

    if (previousValue == null)
      throw TypeError('Reduce of empty array with no initial value');

    for (let index = startIndex; index < this.length; index++) {
      previousValue = callback(previousValue, this[index], index, this);
    }

    return previousValue;
  };
}

```

## Wrapping Up

We saw the working of some commonly used Array methods along with their polyfill implementation, while handling the error cases.

Thank you for reading. Happy coding! 🙂