Retrieving generator references

# Nicholas C. Zakas (9 years ago)

After playing around with generators for most of the day (and pretty much loving all of it), I ended up with a code example for async that looks like this:

var fs = require("fs");

var task;

function readConfigFile() {
     fs.readFile("config.json", function(err, contents) {
         if (err) {
             task.throw(err);
         } else {
             task.next(contents);
         }
     });
}

function *init() {
     var contents = yield readConfigFile();
     doSomethingWith(contents);
     console.log("Done");
}

task = init();
task.next();

The thing that immediately jumped out at me was how poorly the task variable is being managed. So I was poking around trying to see if there was a way to get a reference to the generator instance from within the generator itself so I could pass it around, such as:

function *init() {
     var contents = yield readConfigFile(this);
     doSomethingWith(contents);
     console.log("Done");
}

Of course, this isn't a reference to the generator itself, but rather the this-binding for the function. It struck me, though, that my example could be a lot cleaner if I could get a reference to the generator from the generator function and pass that around rather than having to manage that reference outside.

Now to my question: is there a way to get a reference to the generator from inside the generator function?

(Also, sorry if I'm getting the nomenclature wrong, still trying to wrap my head around the relationship between generators, iterators, and functions.)

# Axel Rauschmayer (9 years ago)

It just goes to show how good promises are for this kind of problem. I’d probably promisify readFile and use Q.spawn().

The best I could come up with for Node.js-style callbacks was the following code.

var fs = require("fs");

function *init() {
    var nextFunc = yield;
    var contents = yield fs.readFile("config.json", nextFunc);
    doSomethingWith(contents);
    console.log("Done");
}

var task = init();
task.next();
task.next(nextify(task));

function nextify(myTask) {
    return function(error, result) {
        if (error) {
            myTask.throw(error);
        } else {
            myTask.next(result);
        }
    };
}
# Kevin Smith (9 years ago)

I can't go into more detail at the moment, but you might want to check out task.js (taskjs.org) and the async function proposal for ES7 ( lukehoban/ecmascript-asyncawait).

# Axel Rauschmayer (9 years ago)

As an aside, I still feel that two concerns are mixed in a generator function:

  • The creation of the generator object. This is where the generator function is like a constructor and where you’d expect this to refer to the generator object.
  • The behavior. This is where the generator function is like a real function.

A result of this mixing of concerns is that using next() to start a generator feels slightly off and that the argument of that first next() invocation is completely ignored.

Alas, I have no idea how to disentangle these concerns, but it would be nice if we were able to.

# Brendan Eich (9 years ago)

Axel Rauschmayer wrote:

As an aside, I still feel that two concerns are mixed in a generator function:

  • The creation of the generator object. This is where the generator function is like a constructor and where you’d expect this to refer to the generator object.

Nope, not a constructor (and if you don't call with 'new', what is 'this', pray tell?).

  • The behavior. This is where the generator function is like a real function.

A result of this mixing of concerns is that using next() to start a generator feels slightly off and that the argument of that first next() invocation is completely ignored.

We've discussed a special generator head form to declare that name, but not for ES6. For now it's Pythonic, except we decided not to throw if the initial .next call passes a non-undefined value.

Alas, I have no idea how to disentangle these concerns, but it would be nice if we were able to.

We're not changing generators at this point, but Nicholas's request for a way to reference the running generator-iterator instance is worth discussing more. It can't and shouldn't be 'this'. Does it violate POLA to provide (via another special form) for all generators? Users can provide their own bindings as noted, so is it a big problem?

I think the task.js reference, and ES7 async/await, point to a better direction: use helpers (libraries now, syntax soon) to automate harder and avoid the need for the "me" reference.

# Axel Rauschmayer (9 years ago)
  • The creation of the generator object. This is where the generator function is like a constructor and where you’d expect this to refer to the generator object.

Nope, not a constructor (and if you don't call with 'new', what is 'this', pray tell?).

I only mean it is “like a constructor” in that it is a function that, when invoked (in some way), returns a fresh object.

The main purpose of a generator function is to specify the behavior of the generator object. But it also serves as its constructing entity. A double duty.

  • The behavior. This is where the generator function is like a real function.

A result of this mixing of concerns is that using next() to start a generator feels slightly off and that the argument of that first next() invocation is completely ignored.

We've discussed a special generator head form to declare that name, but not for ES6. For now it's Pythonic, except we decided not to throw if the initial .next call passes a non-undefined value.

Alas, I have no idea how to disentangle these concerns, but it would be nice if we were able to.

We're not changing generators at this point, but Nicholas's request for a way to reference the running generator-iterator instance is worth discussing more. It can't and shouldn't be 'this'. Does it violate POLA to provide (via another special form) for all generators? Users can provide their own bindings as noted, so is it a big problem?

I’d pass the generator object reference to the generator function via next(), as the first step after creating the generator object. Not pretty, but relatively clean.

I think the task.js reference, and ES7 async/await, point to a better direction: use helpers (libraries now, syntax soon) to automate harder and avoid the need for the "me" reference.

I agree, I can’t think of a solution that wouldn’t be much too complicated for the minor problem that it solves.

# Dmitry Soshnikov (9 years ago)

On Sat, Nov 22, 2014 at 8:03 PM, Brendan Eich <brendan at mozilla.org> wrote:

Axel Rauschmayer wrote:

A result of this mixing of concerns is that using next() to start a generator feels slightly off and that the argument of that first next() invocation is completely ignored.

We've discussed a special generator head form to declare that name, but not for ES6. For now it's Pythonic, except we decided not to throw if the initial .next call passes a non-undefined value.

Yeah, in Python world (where initial JS generators came from) a technique to "prime" a generator is often done via a decorator. JS doesn't have decorators (yet), but it looks like:

// A helper decorator that primes the gen.
function coroutine(func) {
  return (...args) => {
    var gen;
    gen = func(...args);
    gen.next();
    return gen;
  };
}

// Actual gen.
let grep = coroutine(pattern => {
  let line;
  console.log('Looking for ' + pattern);
  while (true) {
    line = yield;
    if (line.indexOf(pattern !== -1)) {
      console.log('Found ' + pattern + ' in ' + line);
    }
  }
  return true;
});

// Usage.
let g = grep('Coffee');

g.send('JavaScript');
g.send('CoffeeScript');

In Python it would be used as:

@coroutine
def grep(pattern):
  ...

Dmitry

# Alex Kocharin (9 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20141123/bbfc41af/attachment

# Nicholas C. Zakas (9 years ago)

Thanks all. My intent wasn't to propose that something was necessary, just to ask if something was available that I was missing. I'll dig in on the await spec and take a good look at the various examples everyone provided.

# Andy Wingo (9 years ago)

On Sun 23 Nov 2014 12:54, Alex Kocharin <alex at kocharin.ru> writes:

Hmm... Interestingly, in Chrome if you do call it with 'new', 'this' would refer to a generator itself. But not in FireFox. I'm playing around with this example:

function *G() {
console.log(this === x)
yield 1
console.log(this === x)
}
x = new G()
x.next()
x.next()

Shows "true, true" for V8 and "false, false" for FF engine.

This is a bug in SM: bugzilla.mozilla.org/show_bug.cgi?id=907742

Andy