25

This code generates an error:

function *giveNumbers() {
    [1, 2, 3].forEach(function(item) {
        yield item;
    })
}

This is probably because yield is inside a function that is not a generator. Is there an elegant way to overcome this? I mean other than:

function *giveNumbers() {
    let list = [1, 2, 3];
    for (let i = 0; i < list.length; i++) {
        yield list[i];
    }
}
Dima Slivin
  • 599
  • 9
  • 19

4 Answers4

13

This is probably because yield is inside a function that is not a generator.

Yes. You cannot use yield from callbacks.

Is there an elegant way to overcome this?

Depends on the use case. Usually there is zero reason to actually want to yield from a callback.

In your case, you want a for…of loop, which is superior to .forEach in almost every aspect anyway:

function *giveNumbers() {
    for (let item of [1, 2, 3])
        yield item;
}
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 3
    Ceterum censeo `forEach` delendam esse a ES6. – Bergi May 16 '17 at 22:01
  • 4
    For those of you who do not know Latin (like me) or are too lazy to google-translate: "`forEach` will certainly be wiped out in ES6". – Palisand Oct 13 '17 at 15:03
  • 1
    @Palisand *[Furthermore, I consider](https://en.wikipedia.org/wiki/Carthago_delenda_est) that `forEach` must (should) be wiped out as of ES6* – Bergi Oct 13 '17 at 15:20
  • @Palisand Admittedly, that's at least what I meant, my Latin is a bit rusty and I have no idea whether "a ES6" is the right choice here – Bergi Oct 13 '17 at 22:22
  • 1
    @Bergi - "there is zero reason to actually want to yield from a callback" - reason is to make asynchronous code synchronous. Why? To make it looks better. Is there some bypass to achieve this? – fider Nov 22 '17 at 16:09
  • @fider You cannot make asynchronous code synchronous, but you can use `await` syntax. [Without `forEach` callbacks](https://stackoverflow.com/a/37576787/1048572), at least. – Bergi Nov 22 '17 at 19:07
  • *My story why this feature would be useful.* I'm trying out a very nice but almost unmaintained package for working with graphs, `ngraph.graph`. Its API exposes only one method for iterating over graph nodes, `forEachNode`, which accepts a callback to be executed for each node. It would be **great** to be able to `yield` a node inside that callback to get an iterator and then use it as one wishes. – N. Kudryavtsev Mar 13 '19 at 19:48
  • @N.Kudryavtsev Well that feature is impossible, as iterators are lazy but `forEach` is not. Your best bet would be fetching all nodes into an array, and then iterating that - you can easily wrap that in a helper function that returns an iterator. Not very efficient, though. Better patch that package. – Bergi Mar 13 '19 at 20:17
5

you can use the yield * syntax.

function *giveNumbers() {
    yield * [1, 2, 3].map(function(item) {
        return item;
    })
}
asmn
  • 127
  • 2
  • 5
1

yield returns the result to the caller.
let's assume the forEach callback is a generator (it's not a problem to set a costume generator there) - it means tha when the callback yield the result - it yields it back to forEach.

Basically, in your question what you attemp to do is:

callback -> yields to forEach -> yields to giveNumbers -> yields to caller

So, forEach should yield the result back to giveNumbers. but since forEach doesn't work like this, it's impossible without re-prototype arrays with costume forEach.

Actually, you second snippet is the most elegant to begin with.

David Haim
  • 25,446
  • 3
  • 44
  • 78
0

You can also use while and pass arguments as such (Demo)

function *giveNumbers(array, start) {
    var index = start || 0;
    while(array.length > index + 1) {
        yield array[index++];
    }
    return array[index];
}


var g = giveNumbers([1,2,3], 0);

var finished = false;

while(!finished) {
    var next = g.next();
    console.log(next.value);
    if(next.done) {
        finished = true;
    }
}
Taku
  • 5,639
  • 2
  • 42
  • 31