43

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;
 });
};
asked Mar 28, 2015 at 4:14
3
  • 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. Commented Mar 28, 2015 at 4:24
  • 1
    I don't think it is possible... a classic for loop stmt will be the right fit Commented Mar 28, 2015 at 4: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. Commented Sep 28, 2015 at 10:24

3 Answers 3

44

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.
75k15 gold badges111 silver badges129 bronze badges
answered Mar 28, 2015 at 9:35
Sign up to request clarification or add additional context in comments.

Comments

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
answered Mar 28, 2015 at 5:52

2 Comments

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.
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?
1

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.

answered Apr 26, 2023 at 11:01

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.