Search code examples
javascriptgeneratorchain

How to extend the array with functions that are generators and able to chain them?


How do I extend Array with functions that returns a generator object and be able to chain them?

I have tried extending the Array to see if I can chain generator functions. The first call works, but the next chain would not, because it returns a generator object and I have no idea how to extend that.

Array.prototype.select = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) yield fn(item);
}

Array.prototype.where = function* (fn) {
    let it = this[Symbol.iterator]();
    for (let item of it) {
        if (fn(item)) yield item;
    }
}

I want to be able to chain generators like this to an array

let result = arr.select(v => v * 2)
                .where(v => v % 3 === 0);

for (let item of result) console.log(item);

Solution

  • Without modifying Object, you can still make these methods chainable by using a technique I've coined as "superclassing".

    You start by defining your base class which Array will extend, then you modify the prototype chain of Array to artificially extend your base class.

    Note how the select() and where() methods encapsulate your original generator functions to return new instances of the class so that the methods are chainable.

    class Enumerable {
      constructor (getIterator) {
        this[Symbol.iterator] = getIterator;
      }
    
      select (fn) {
        return new Enumerable(function * () {
          for (const value of this) {
            yield fn(value);
          }
        }.bind(this));
      }
    
      where (fn) {
        return new Enumerable(function * () {
          for (const value of this) {
            if (fn(value)) yield value;
          }
        }.bind(this));
      }
    }
    
    Object.setPrototypeOf(Array, Enumerable);
    Object.setPrototypeOf(Array.prototype, Enumerable.prototype);
    
    const array = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    const result = array
      .select(v => v * 2)
      .where(v => v % 3 === 0);
    
    for (const item of result) {
      console.log(item);
    }