最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - yielding from an iterator callback used inside a generator - Stack Overflow

programmeradmin2浏览0评论

Has anyone tried to get Underscore JS or lodash (or any ES5 standard functions for that matter) working with generators?

If we have an array var myArray = [1,2,3,4,6]; We want to forEach over it.

In a non generator case you would simply

myArray.forEach(function(k) {
  console.log(k);
});

However, when you can't yield inside a non generator function, so if inside this loop we had to do some async work, you would need to do the following.

var foreach = function* (arr, fn) {
  var i;
  for (i = 0; i < arr.length; i++) {
    yield * fn(arr[i], i);
  }
};

yield* foreach(myArray, function* (k) {
  var a = yield fs.readFile();
});

Which kind of sucks.

Anyone know of a way to get anonymous functions working with generators? We kind of lose the entire lodash library because of this.

Note: I'm using Traceur to pile my code into ES6 with generators turned on.
Note: I'm not using co(). I'm using a custom generator function seen below

var run = function(generatorFunction) {
  var generatorItr = generatorFunction(resume);
  function resume(callbackValue) {
    generatorItr.next(callbackValue);
  }
  generatorItr.next();
};

Has anyone tried to get Underscore JS or lodash (or any ES5 standard functions for that matter) working with generators?

If we have an array var myArray = [1,2,3,4,6]; We want to forEach over it.

In a non generator case you would simply

myArray.forEach(function(k) {
  console.log(k);
});

However, when you can't yield inside a non generator function, so if inside this loop we had to do some async work, you would need to do the following.

var foreach = function* (arr, fn) {
  var i;
  for (i = 0; i < arr.length; i++) {
    yield * fn(arr[i], i);
  }
};

yield* foreach(myArray, function* (k) {
  var a = yield fs.readFile();
});

Which kind of sucks.

Anyone know of a way to get anonymous functions working with generators? We kind of lose the entire lodash library because of this.

Note: I'm using Traceur to pile my code into ES6 with generators turned on.
Note: I'm not using co(). I'm using a custom generator function seen below

var run = function(generatorFunction) {
  var generatorItr = generatorFunction(resume);
  function resume(callbackValue) {
    generatorItr.next(callbackValue);
  }
  generatorItr.next();
};
Share Improve this question edited Jul 26, 2014 at 19:25 Bergi 667k161 gold badges1k silver badges1.5k bronze badges asked Jul 26, 2014 at 3:41 Sean ClarkSean Clark 1,4561 gold badge17 silver badges32 bronze badges 6
  • 2 Maybe it's just me, but I don't understand what exactly the problem is. It sounds more like the problem is using e.g. forEach with generators. – Felix Kling Commented Jul 26, 2014 at 4:51
  • 1 Well yes, but that's not the real* problem. The problem is using yield inside of a non generator function. Which ForEach will use 90% of the time. Not to mention _.find(), _.filter(), Array.reduce(), Array.forEach(), Array.map(). All of them are useless if you need to yield anything inside. – Sean Clark Commented Jul 26, 2014 at 17:07
  • 4 In case of forEach, you can simply use for (var e of arr) { yield doSomethingWith(e); } or a normal for loop. For other methods such as filter or reduce, I don't see how using a generator would even be useful. The filter callback must return a boolean. How exactly would using a generator make sense here? – Felix Kling Commented Jul 26, 2014 at 18:25
  • Well, any answer I can think of would be inefficient code. Doing work inside a loop. But anyway, if you have a list of mp3 urls and you need to filter that list down to ones that actually exist on the filesystem. You would normally do a filter on your list, check the FS on each iteration, and Promise.all() when they all are done. Using generators we can't use filter. We have to loop and store a 2nd array of the results. – Sean Clark Commented Jul 26, 2014 at 18:34
  • @SeanClark: Notice that Promise.all would have started the fs queries in parallel, while the generator solution you seem to be looking for would be sequential. – Bergi Commented Jul 26, 2014 at 19:29
 |  Show 1 more ment

2 Answers 2

Reset to default 1

If I'm understanding your problem correctly, it's essentially that you're trying to do something (iterate until a good stopping point is found) in an asynchronous way, in a language (JS) which is really designed around synchronicity. In other words, while you could normally do:

_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = x % 2 == 0;
    return shouldWeStopLogging;
});

you instead want to make the "should we stop looping" code break from normal execution, and then e back, which isn't possible with traditional JS (yield is relatively new to the language) and thus isn't possible in Underscore/Lodash:

_([1,2,3]).any(function(x) {
    var shouldWeStopLooping = $.ajax(...); // Doesn't work; code keeps going
    return shouldWeStopLogging;
});

There are two approaches you could take, neither of which are ideal.

As mentioned in the ments, one approach would be to do all your "deferred" work first, then iterate:

var workInProgress = _([1,2,3]).map(someAjaxOperation);
$.when.apply(workInProgress).done(doSomethingBasedOnAjaxResults);

But (as also noted in the ments) that isn't quite the same, as you wind up doing the AJAX work on all of the elements of your array (vs. a true generator which would only iterate through as many as needed to find a "winner").

Another approach would be to eliminate the asynchronicity. jQuery allows you to pass async: false to an AJAX request, which "solves" the problem by letting you use Underscore/Lodash/whatever ... but it also locks your user's browser up for as long as it takes to do the AJAX work, which probably isn't what you want.

Unfortunately if you want to use a library like Underscore/Lodash those are the only options I can see. Your only other option would be to write your own Underscore/Lodash mix-in, which really isn't that hard. I'd remend doing this, as it would allow you still leverage all the other great functions in those libraries while still iterating in a consistent way.

This works:

class Observable {
    constructor(ids) {
        this.generator = 
            async function *generator() {
                let response;
                let people;
                
                for (const id of ids) {
                    response = await fetch(`https://swapi.dev/api/people/${id}`);
                    people = await response.json();
                    yield people.name;            
                }
            };
    }

    subscribe(callback) {
        (async function() {
            for await (const i of this.generator()) {
                callback(i);
            }
        }).bind(this)();
    }
}

const observable = new Observable([1, 2, 3]);
observable.subscribe(console.log);
发布评论

评论列表(0)

  1. 暂无评论