Sean Clark
Sean Clark

Reputation: 1456

yielding from an iterator callback used inside a generator

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

Upvotes: 24

Views: 2889

Answers (1)

machineghost
machineghost

Reputation: 35750

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 come 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 comments, 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 comments) 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 recommend doing this, as it would allow you still leverage all the other great functions in those libraries while still iterating in a consistent way.

Upvotes: 1

Related Questions