Can I yield from an inner function?
Asked Answered
R

3

41

With ES6 generators, I see code like this:

var trivialGenerator = function *(array) {
    var i,item;
    for(var i=0; i < array.length; i++){
        item = array[i];
        yield item;
    };
};

Is it possible to write something more like the code below instead?

var trivialGenerator = function *(array) {
    array.forEach(function *(item){
        yield item;
    });
};
Resilient answered 28/3, 2015 at 4:14 Comment(3)
This doesn't make sense. you simply regenerate the input array. In any case, the answer is no. In your case though you could use a for..of loop.Cork
I don't think it is possible... a classic for loop stmt will be the right fitWeathering
The classic for loop is by no means an abomination. In fact, as you are seeing, in part because of generators it is making a comeback.Filmer
I
43

No, you can't use yield inside of the inner function. But in your case you don't need it. You can always use for-of loop instead of forEach method. It will look much prettier, and you can use continue, break, yield inside it:

var trivialGenerator = function *(array) {
    for (var item of array) {
        // some item manipulation
        yield item;
    }
}

You can use for-of if you have some manipulations with item inside it. Otherwise you absolutely don't need to create this generator, as array has iterator interface natively.

Ida answered 28/3, 2015 at 9:35 Comment(0)
F
12

No, you can't yield from a callback (technically, it's not an "inner function", which means something else). There's obviously no way to call forEach with the equivalent of *, or, if the callback itself is a generator, to tell forEach to invoke the callback with yield *.

One alternative is to write a function forEachGen, as follows:

function *forEachGen(array, fn) { for (var i of array) yield *fn(i); }

essentially moving the for-loop into forEachGen. Defining a little sample generator as

function *yieldSelf(item) { yield item; }

forEachGen would be used as

yield *forEachGen(array, yieldSelf);

This assumes the callback is a generator itself, as you seem to imply you want in your example. If the callback were a ROF (regular old function), such as

function returnSelf(item) { return item; }

Then it would be

function *forEachGen(array, fn) { for (var i of array) yield fn(i); }

used as

yield *forEachGen(array, returnSelf);

If you don't mind adding this to the array prototype, then

Object.defineProperty(Array.prototype, 'forEachGen', { value :
    function *(fn) { for (i of this) yield fn(i); }
});

then do

yield *array.forEachGen(yieldSelf)

You may be interested in http://fitzgen.github.io/wu.js/, which defines a wrapper for generators with methods such as forEach on the wrapper.

async / await

With await, you should be able to do the following.

Define a trivial callback which just returns a promise for itself.

async function returnSelf(item) { return await item; }

forEachAsync maps the input array into an array of promises, and uses await * to create and return a promise for all the individual promises being ready.

async function forEachAsync(values, fn) {
  return await *values.map(returnSelf);
}

We can treat the result as a regular promise and print it out in a then:

forEachAsync([1,2,3], returnSelf) .
  then(result => console.log(result);

or use a little IIFE async wrapper to do wait for the result and then print it out:

(async function() { 
    console.log(await forEachAsync([1,2,3], returnSelf));
})();

Tested using

babel-node --experimental test.js
Filmer answered 28/3, 2015 at 5:52 Comment(2)
As I defined it, I think it is non-iterable, or do you mean non-enumerable, or did I miss something? Default for enumerable is false.Filmer
I'm having a similar problem but I cannot move the for-loop into forEachGen as in my case it's not an array, it's a stream where I'm getting my items from. So far, the only solution I could think of is to buffer the items and then iterate over them with a generator function. But this heavily breaks the "infinite nature" of streams and will reduce throughput. Do you have any idea on how to deal with something like this?Sputter
C
0

You can't but you can create your own wrapper to convert the function into a generator. Here's how I did it:

export async function* createListFilesIterator(
  worker: DriveWorker,
): AsyncGenerator<FilesResp, void, unknown> {
  const messages: FilesResp[] = [];
  let processed = 0;
  let waited = 0;
  let done = false;

  worker.onmessage = (msg) => {
    const { data: evt } = msg;
    if (evt.type === "files") {
      messages.push(evt);
    } else if (evt.type === "done") {
      done = true;
    }
  };

  while (processed < messages.length || (!done && waited <= 16)) {
    if (processed < messages.length) {
      yield messages[processed];
      waited = 0;
      processed += 1;
    } else {
      waited += 1;
      await new Promise((resolve) => {
        setTimeout(resolve, 1000 * waited * 0.5);
      });
    }
  }
}

With this method I can convert my worker instance into an iterator which I can loop through with:

for await (const evt of createListFilesIterator(worker)) {
   ...

Of course I could make it a lot more simpler by just returning a promise with an onmessage eventlistener inside of it but I just wanted to see whether this was doable / made sense. I think when you grow beyond two return types generators become much cleaner and easier to use than event listeners. But that's my opinion, for certain.

Crackerbarrel answered 26/4, 2023 at 11:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.