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"
]
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With