2014-12-10 4 views
4

Я пытаюсь построить привязываемый JavaScript API. (Я запускаю это в последней версии V8 с включенными итераторами и генераторами.) В приведенном ниже примере setState является цепным. Он также позволяет вам вызывать его без необходимости явно создавать новый экземпляр Builder. Вспомогательная функция chain() обрабатывает это и автоматически возвращает этот экземпляр, поэтому setState не должен беспокоиться об этом. (Первоклассные функции для победы!)Эквивалент функции.prototype.apply для генератора ES6

В дополнение к методам, связанным с цепью, я хочу использовать некоторые «завершающие» методы. Кикер состоит в том, что эти «терминаторы» являются генераторами. Генераторы приводят к внутреннему состоянию экземпляра Builder. Проблема в том, что я не могу вычислить эквивалент f.apply(that, arguments) для генератора. Я хочу, чтобы иметь возможность вызвать генератор и установить его this контекст во время выполнения, так же, как вы можете делать с Function.prototype.apply и Function.prototype.call.

Работы вокруг, комментируется Yuck!, чтобы выставить как делегированное и оригинального генератор на Builder.prototype, называя оригинал от делегированного. Есть ли способ реализовать эквивалентную упаковку, такую ​​как метод chain, без необходимости подвергать _generate методу на 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; 

Который возвращает

[ 
    "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" 
] 

ответ

2

Ключ был в анонимной обертки генератора yield *gen.apply(that, arguments).

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

// Reuses or creates a Builder instance and makes it `this` for calling `f`. 
// Returns the Builder instance. 
function chain(f) { 
    return function() { 
    var that = (this instanceof Builder) ? this : new Builder(); 
    f.apply(that, arguments); // Pass through arguments 
    return that; 
    } 
} 

// Similar to `chain()` to create a Builder instance if it doesn't exist. 
// Generators are terminal, though, so this returns the FunctionGenerator. 
function delegate(gen) { 
    return function*() { 
    var that = (this instanceof Builder) ? this : new Builder(); 
    that.setState('delegated', true);    
    yield *gen.apply(that, arguments); 
    } 
} 

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; 
3

Проблема заключается в том, что я не могу понять, эквивалент f.apply (что, аргументы) для генератора.

Судя по ES6 draft, вам не нужен эквивалент - вы можете просто использовать выражение как есть. Функции генератора (которые строят экземпляры генератора) являются функциями, и они наследуют (через Generator.prototype) от Function.prototype; вы можете использовать .call и .apply на них, как и на любую другую функцию.

Так должно работать:

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 
    } 
} 
+0

Да, вы правы, например: (функция *() {}) InstanceOf функции; // true var gen = function *() { yield this.random(); } gen.apply (Math, gen) .next(). Value; Я должен делать что-то еще не так в моем коде. Позволь мне еще раз нанести удар. –

+0

Обратите внимание, что моя функция 'delegate' больше не возвращает функцию-генератор * - возвращает нормальную' функцию' (которая возвращает генератор). – Bergi

+0

Справа. Я скорректировал свое решение ниже, чтобы вернуть генератор, который дает результат применения генератора. –

Смежные вопросы