DEV Community

Cover image for .reduce() Polyfill
Vikrant Bhat
Vikrant Bhat

Posted on

.reduce() Polyfill

The full source code for the polyfill is available an the end ⬇️

What is a polyfill?

A polyfill is a piece of code used to provide modern functionality on older browsers that do not natively support it.

Polyfill for Array.prototype.reduce()

We will be writing the polyfill in steps, and in each step we will cover a test case.

Note: In first 3 cases we will handle the error conditions

Case 1: Check if this exists, reduce is not called on null or undefined

To write the polyfill, we will create a function called myReduce which will take two arguments which are, a callBack function and the initialValue namely.

function myReduce(callback, initialValue) {...}
Enter fullscreen mode Exit fullscreen mode

In the current case, this refers to the array on which our myReduce is called.

For example,

[1, 2, 3].myReduce(...)
Enter fullscreen mode Exit fullscreen mode

then, in the above example this refers to the array [1, 2, 3].

moving ahead, in the current case, we have to check if this exists and reduce is not called on null or undefined, and if it is, then throw a TypeError ☠️.

So, the code for it will look like:

function myReduce(callback, initialValue) {
    if (this === null || this === undefined) {
        throw new TypeError(
            'Array.prototype.myReduce is called on null or undefined'
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Case 2: Check if callback is a function

To handle this case, we add another check for callback, and throw a TypeError if otherwise.

function myReduce(callback, initialValue) {
    if (this === null || this === undefined) {
        throw new TypeError(
            'Array.prototype.myReduce is called on null or undefined'
        );
    }

    if (!callback || typeof callback !== 'function') { // check for callback to exist, and it should be a function
        throw new TypeError(`${callback} is not a function`);
    }
}
Enter fullscreen mode Exit fullscreen mode

Case 3: Reduce on empty array with no initialValue should throw error

To handle this case, we add another if check, and throw a TypeError if otherwise.

function myReduce(callback, initialValue) {
    if (this === null || this === undefined) {
        throw new TypeError(
            'Array.prototype.myReduce is called on null or undefined'
        );
    }

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

    if (!this.length) { // i.e. if array is empty
        if (arguments.length < 2) { // i.e.  if there are less than two arguments, then that means `initialValue` is missing, since callback is a mandatory argument for myReduce function. 
            throw new TypeError('Reduce of empty array with no initial value');
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note: Read more about arguments here

Case 4: Invoked on empty array with initialValue, returns initialValue

For this, we add an else if block, and check if there are exactly 2 arguments

Note: for the sake of readability, I will be only showing the the code under discussion in code snippets.

function myReduce(callback, initialValue) {
    ...
    ...

    if (!this.length) { // i.e. if array is empty
        if (arguments.length < 2) { // i.e.  if there are less than two arguments
            throw new TypeError('Reduce of empty array with no initial value');
        } else if (arguments.length === 2) { // This check means that there are exactly 2 arguments, which will namely be -> `callback` and `initialValue`
            return initialValue; // so, in that case return initialValue
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

For example,

function sum(a, b) {
    return a + b
}

[].myReduce(sum, 5) // here `sum` is the callback function and 5 is the initialValue.

// OUTPUT
5
Enter fullscreen mode Exit fullscreen mode

Case 5: Invoked with initialValue

We initialize 2 variables:
k equal to 0
and, acc equal to initialValue (acc is short for accumulator, because it will be used to accumulate the result)

acc is set to initialValue because we want our accumulated result to already have the initialValue in it.

Note: You may be wondering why are we using the var keyword to declare variables (which is usually a discouraged practice in modern web development), well since the whole point of a polyfill is that it should run in old browsers that don't support latest features yet, hence we are using var which has been a part of JS since the start.

We also start a while loop, which runs until k is less than the array's length, and inside the while loop we update the acc variable to callback(acc, this[k], k, this) and increment k

Now, callback(acc, this[k], k, this) might seem complex at first, so let me break it down.

By definition, a callback function in reduce has to take 4 parameters:

  1. accumulator (acc)
  2. currentValue (this[k] i.e. the current value of of the array under iteration)
  3. currentIndex (k)
  4. array (this)
function myReduce(callback, initialValue) {
    ...
    ...
    ...

    var k = 0;
    var acc = initialValue;

    while (k < this.length) {

        acc = callback(acc, this[k], k, this);

        k++; // increment k
    }

    return acc;
}
Enter fullscreen mode Exit fullscreen mode

For example,

function sum(a, b) {
    return a + b
}

[1, 2, 3].myReduce(sum, 1) // here 1 is the initialValue.

// OUTPUT
7
Enter fullscreen mode Exit fullscreen mode

Case 6: Invoked without initialValue

For this case, we check if arguments.length is less than 2, because it means that initialValue has not been provided, and if it is less than two then we equate the accumulator to this[k++], which means the first value of the array (this[0], since k = 0 initially). We also post-increment k by using k++, because we want the while loop to start from index 1 and not from index 0 since the accumulator already has the initialValue as the value at index 0 in the array.

function myReduce(callback, initialValue) {
...
...
...

    var k = 0;
    var acc = initialValue;

    if(arguments.length < 2){
       acc = this[k++] // update `acc` and increment `k`
    }

    // rest of the code stays the same

    while (k < this.length) {

        acc = callback(acc, this[k], k, this);

        k++;
    }

    return acc;
}
Enter fullscreen mode Exit fullscreen mode

For example,

function sum(a, b) {
    return a + b
}

[1, 2, 3].myReduce(sum) // no initialValue provided.

// OUTPUT
6
Enter fullscreen mode Exit fullscreen mode

Complete code

function myReduce(callback, initialValue) {
    if (this === null || this === undefined) {
        throw new TypeError(
            'Array.prototype.myReduce is called on null or undefined'
        );
    }

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

    if (!this.length) {
        if (arguments.length < 2) {
            throw new TypeError('Reduce of empty array with no initial value');
        } else if (arguments.length === 2) {
            return initialValue;
        }
    }

    var k = 0;
    var acc = arguments.length < 2 ? this[k++] : initialValue;

    while (k < this.length) {
        if (Object.prototype.hasOwnProperty.call(this, k)) {
            acc = callback(acc, this[k], k, this);
        }
        k++;
    }

    return acc;
}
Enter fullscreen mode Exit fullscreen mode

This blogpost is inspired by this youtube video which does a great job at explanation.


Thankyou for reading this article! Let's become friends on twitter🐤 also :)

Oldest comments (0)