Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of Function.prototype.apply for an ES6 generator

I'm trying to build a chainable JavaScript API. (I’m running this in a recent version of V8 with iterators and generators enabled.) In the example below, setState is chainable. It also allows you to call it without having to explicitly create a new Builder instance. The chain() helper function handles that and automatically returns that instance so setState doesn't have to worry about it. (First-class functions for the win!)

In addition to the chainable methods I want some "terminating" methods. The kicker is that these "terminators" are generators. The generators yield internal state of a Builder instance. The problem is that I can’t figure out an equivalent to f.apply(that, arguments) for a generator. I want to be able to call a generator and set its this context at runtime, just like you're able to do with Function.prototype.apply and Function.prototype.call.

The work-around, commented with Yuck!, is to expose both the delegated and the original generator on the Builder.prototype, calling the original from the delegated. Is there any way to implement the equivalent wrapping like the chain method without having to expose the _generate method on the Builder.prototype?

function Builder() { this.initialState = 'initialState'; };
Builder.prototype.setState = chain(function(k, v) { this[k] = v; });
Builder.prototype.generate = delegate(generate, '_generate'); // Yuck!
Builder.prototype._generate = generate;


function chain(f) {
  return function() {
    var that = (this instanceof Builder) ? this : new Builder();
    f.apply(that, arguments); // Pass through arguments
    return that;
  }
}

function delegate(gen, _gen) {
  return function*() {
    var that = (this instanceof Builder) ? this : new Builder();
    that.setState('delegated', true);               
    yield *that[_gen](); // Yuck!
  }
}

function *generate(opts) {
  var i = 0;
  for(var i = 0; i < 10; i++) {
    yield [Object.keys(this), opts, i].join(',');
  }
}

// Set up a namespace
var ns = {};
ns.setState = Builder.prototype.setState;
ns.generate = Builder.prototype.generate;


var itr = ns
//  .setState('a', 'A')
//  .setState('b', 'B')
//  .setState('c', 'C')
  .generate('options');


var out = [];
for(var value of itr) { out.push(value); }
out;

Which returns

[
  "initialState,delegated,,0",
  "initialState,delegated,,1",
  "initialState,delegated,,2",
  "initialState,delegated,,3",
  "initialState,delegated,,4",
  "initialState,delegated,,5",
  "initialState,delegated,,6",
  "initialState,delegated,,7",
  "initialState,delegated,,8",
  "initialState,delegated,,9"
]
like image 864
Justin Makeig Avatar asked Dec 10 '14 21:12

Justin Makeig


1 Answers

The problem is that I can’t figure out an equivalent to f.apply(that, arguments) for a generator.

Judging from the ES6 draft, you don't need an equivalent - you can just use the expression as is. Generator functions (that construct generator instances) are functions, and they inherit (via Generator.prototype) from Function.prototype; you can use .call and .apply on them as on any other function.

So the following should work:

function delegate(gen) {
  return function() {
    var that = (this instanceof Builder) ? this : new Builder();
    that.setState('delegated', true);               
    return gen.apply(that, arguments); // Pass through arguments
  }
}
like image 96
Bergi Avatar answered Oct 11 '22 22:10

Bergi