10

What's the correct way of using the new ES5 array functions with ES6 generators? Do I have to explicitly convert the iterable into an array first, or is there a better way? For example:

function* range(low, high) {
    var i = low;
    while(i < high)
        yield i++;
}

// Sum of numbers in range, doesn't work
console.log(range(0, 10).reduce((x,y) => x + y));
Lesleh
  • 1,665
  • 15
  • 24
  • possible duplicate of [Why do generators not support map()?](https://stackoverflow.com/q/31232415/1048572) – Bergi Nov 14 '17 at 22:48

3 Answers3

10

Generator functions return Iterator objects. The Iterator API does not include higher order Array methods such as map, reduce etc, so you need to build an intermediate Array (or use a library like wu.js).

You can use the spread operator to concisely build an Array from a (finite) iterator:

var sum = [...range(0, 10)].reduce((e, i) => e + i)
joews
  • 29,767
  • 10
  • 79
  • 91
  • It works, I just wondered if there were a built-in way of dealing with it, without having to create an intermediate array (say for example, if the array created were to be quite large). It doesn't seem like there is though. – Lesleh Feb 16 '15 at 12:25
  • I think it isn't possible directly because iterators can be unbounded. How would you reduce over an infinite sequence? – joews Feb 16 '15 at 12:28
  • Yup, good point. wu.js looks like what I need though, so I'll accept this answer. – Lesleh Feb 16 '15 at 12:36
5

Build the array using Array.from:

console.log(Array.from(range(0, 10)).reduce((x,y) => x + y));

Array.from creates an array from an iterable. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from.

If you want to do the reduce without creating the array, then you'll end up needing to do something like:

var sum = 0;
for (e of range(0, 10)) sum += e;
3

Since Array.from does not work on Chrome at the current time, I needed another way to convert an Iterator into an Array.

(though of course you can shim it with a polyfill)

function iterator2Array(iterator) {
    var result = [];
    for (var item in iterator) {
        result.push(item)
    }
    return result;
}

For similar reasons I add a "toArray" to the prototype of a Map so that I basically convert an iterator into an Array so that you can use its functional-oriented methods; of course each item of the array will be a [key, value] tuple (exactly like in its Map.entries())

if (!Map.prototype.toArray) {
    /**
     * Transforms a map into an Array of 'tuples' [[key, value], ...]
     */
    Map.prototype.toArray = function () {
        var result = [];

        for (var item of this) {
            result.push(item);
        }

        return result;
    }
}

var m = new Map([[0, 0], ['a', 'A']]);
m.toArray()

Then you can use it as an array - remember the [key, value] approach though!

m.toArray().map(
    function(item, index, array) {
        var key = item[0],
        value = item[1];
        console.log(key + ": " + value);
        return value;
});

This will return the values of the map (ok not superuseful of course!)

If you prefer a more standar looking loop:

var i = iterator.entries(),
    result = [],
    value;
while (value = i.next().value) {
    result.push(value);
}
Stefano
  • 18,083
  • 13
  • 64
  • 79