Can I yield from an inner function?

2019-03-11 01:30发布

问题:

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;
    });
};

I'm asking because the classic for loop is an abomination.

回答1:

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.



回答2:

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