Javascript yield from the function nested inside generator
Asked Answered
M

5

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];
    }
}
Meuser answered 5/12, 2015 at 11:18 Comment(0)
Z
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;
}
Zwiebel answered 5/12, 2015 at 15:10 Comment(8)
Ceterum censeo forEach delendam esse a ES6.Zwiebel
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".Stressful
@Stressful Furthermore, I consider that forEach must (should) be wiped out as of ES6Zwiebel
@Stressful 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 hereZwiebel
@Zwiebel - "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?Dampproof
@Dampproof You cannot make asynchronous code synchronous, but you can use await syntax. Without forEach callbacks, at least.Zwiebel
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.Kalgoorlie
@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.Zwiebel
E
5

you can use the yield * syntax.

function *giveNumbers() {
    yield * [1, 2, 3].map(function(item) {
        return item;
    })
}
Effluvium answered 8/4, 2018 at 12:41 Comment(1)
this does not yield to parent scopeSiqueiros
D
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.

Doll answered 5/12, 2015 at 11:46 Comment(0)
B
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;
    }
}
Brunel answered 26/8, 2016 at 10:54 Comment(0)
G
0

Sometimes there's no way to yield directly like with the .map example in the other answer. For example, if we have a tree of nodes, and each node has a traverse method to traverse the tree, accepting a callback that runs for each node, we can create an additional array to yield:

function nodes(obj) {
  const nodes = []
  obj.traverse(node => nodes.push(node))
  yield * nodes
}

for (const node of nodes(someTree)) console.log(node)

If we have access to the array, we can directly loop to avoid an extra array:

function nodes(obj) {
  yield obj
  for (const node of obj.children) {
    yield * nodes(node)
  }
}

for (const node of nodes(someTree)) console.log(node)
Grazier answered 16/2 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.