39

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;
    });
};
Max Heiber
  • 14,346
  • 12
  • 59
  • 97
  • 1
    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. – Polity Mar 28 '15 at 04:24
  • 1
    I don't think it is possible... a classic for loop stmt will be the right fit – Arun P Johny Mar 28 '15 at 04:24
  • 10
    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. –  Sep 28 '15 at 10:24

3 Answers3

42

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.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
alexpods
  • 47,475
  • 10
  • 100
  • 94
11

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
  • 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`. –  Mar 28 '15 at 06:37
  • 1
    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? – vanthome Sep 25 '15 at 07:37
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.

TeemuK
  • 2,095
  • 1
  • 18
  • 17