Подтвердить что ты не робот

Проверьте, является ли функция генератором

Я играл с генераторами в Nodejs v0.11.2, и мне интересно как я могу проверить, что аргумент моей функции является функцией генератора.

Я нашел этот способ typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function), но я не уверен, что это хорошо (и работает в будущем).

Каково ваше мнение об этой проблеме?

4b9b3361

Ответ 1

Мы говорили об этом на личных встречах TC39, и мы намерены не раскрывать способ определить, является ли функция генератором или нет. Причина в том, что любая функция может возвращать итерируемый объект, поэтому не имеет значения, является ли это функцией или генераторной функцией.

var iterator = Symbol.iterator;

function notAGenerator() {
  var  count = 0;
  return {
    [iterator]: function() {
      return this;
    },
    next: function() {
      return {value: count++, done: false};
    }
  }
}

function* aGenerator() {
  var count = 0;
  while (true) {
    yield count++;
  }
}

Эти два ведут себя одинаково (минус .throw(), но могут быть добавлены тоже)

Ответ 2

В последней версии nodejs (я проверял с v0.11.12) вы можете проверить, равно ли имя конструктора GeneratorFunction. Я не знаю, какая версия это вышла, но она работает.

function isGenerator(fn) {
    return fn.constructor.name === 'GeneratorFunction';
}

Ответ 3

Я использую это:

var sampleGenerator = function*() {};

function isGenerator(arg) {
    return arg.constructor === sampleGenerator.constructor;
}
exports.isGenerator = isGenerator;

function isGeneratorIterator(arg) {
    return arg.constructor === sampleGenerator.prototype.constructor;
}
exports.isGeneratorIterator = isGeneratorIterator;

Ответ 4

это работает в node и в firefox:

var GeneratorFunction = (function*(){yield undefined;}).constructor;

function* test() {
   yield 1;
   yield 2;
}

console.log(test instanceof GeneratorFunction); // true

jsfiddle

Но это не работает, если вы связываете генератор, например:

foo = test.bind(bar); 
console.log(foo instanceof GeneratorFunction); // false

Ответ 5

Библиотека TJ Holowaychuk co имеет лучшую функцию для проверки того, является ли что-то функцией генератора. Вот исходный код:

function isGeneratorFunction(obj) {
   var constructor = obj.constructor;
   if (!constructor) return false;
   if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
   return isGenerator(constructor.prototype);
}

Ссылка: https://github.com/tj/co/blob/717b043371ba057cb7a4a2a4e47120d598116ed7/index.js#L221

Ответ 6

В node 7 вы можете instanceof от конструкторов обнаружить функции генератора и асинхронные функции:

const GeneratorFunction = function*(){}.constructor;
const AsyncFunction = async function(){}.constructor;

function norm(){}
function*gen(){}
async function as(){}

norm instanceof Function;              // true
norm instanceof GeneratorFunction;     // false
norm instanceof AsyncFunction;         // false

gen instanceof Function;               // true
gen instanceof GeneratorFunction;      // true
gen instanceof AsyncFunction;          // false

as instanceof Function;                // true
as instanceof GeneratorFunction;       // false
as instanceof AsyncFunction;           // true

Это работает для всех обстоятельств в моих тестах. В приведенном выше комментарии говорится, что он не работает для выражений функции сгенерированных генераторов, но я не могу воспроизвести:

const genExprName=function*name(){};
genExprName instanceof GeneratorFunction;            // true
(function*name2(){}) instanceof GeneratorFunction;   // true

Единственная проблема заключается в том, что свойство .constructor экземпляров может быть изменено. Если кто-то действительно решил вызвать проблемы, они могут сломать его:

// Bad people doing bad things
const genProto = function*(){}.constructor.prototype;
Object.defineProperty(genProto,'constructor',{value:Boolean});

// .. sometime later, we have no access to GeneratorFunction
const GeneratorFunction = function*(){}.constructor;
GeneratorFunction;                     // [Function: Boolean]
function*gen(){}
gen instanceof GeneratorFunction;      // false

Ответ 7

Документация javascript Mozilla описывает метод Function.prototype.isGenerator MDN API. Nodejs, похоже, не реализует его. Однако, если вы хотите ограничить свой код определением генераторов только с помощью function* (без возвращаемых итерационных объектов), вы можете увеличить его, добавив его непосредственно с проверкой на совместимость:

if (typeof Function.prototype.isGenerator == 'undefined') {
    Function.prototype.isGenerator = function() {
        return /^function\s*\*/.test(this.toString());
    }
}

Ответ 8

Как сказал @Erik Arvidsson, нет стандартного способа проверить, является ли функция функцией генератора. Но вы можете, конечно, просто проверить интерфейс, функция генератора выполняет:

function* fibonacci(prevPrev, prev) {

  while (true) {

    let next = prevPrev + prev;

    yield next;

    prevPrev = prev;
    prev = next;
  }
}

// fetch get an instance
let fibonacciGenerator = fibonacci(2, 3)

// check the interface
if (typeof fibonacciGenerator[Symbol.iterator] == 'function' && 
    typeof fibonacciGenerator['next'] == 'function' &&
    typeof fibonacciGenerator['throw'] == 'function') {

  // it safe to assume the function is a generator function or a shim that behaves like a generator function

  let nextValue = fibonacciGenerator.next().value; // 5
}

Вот оно.

Ответ 9

Я проверил, как koa делает это, и они используют эту библиотеку: https://github.com/ljharb/is-generator-function.

Вы можете использовать его так:

const isGeneratorFunction = require('is-generator-function');
if(isGeneratorFunction(f)) {
    ...
}

Ответ 10

Трудность, о которой здесь не говорится, заключается в том, что если вы используете метод bind для функции генератора, он изменяет имя своего прототипа с "GeneratorFunction" на "Function".

Нет нейтрального метода Reflect.bind, но вы можете обойти это, сбросив прототип связанной операции с исходной операцией.

Например:

const boundOperation = operation.bind(someContext, ...args)
console.log(boundOperation.constructor.name)       // Function
Reflect.setPrototypeOf(boundOperation, operation)
console.log(boundOperation.constructor.name)       // GeneratorFunction

Ответ 11

Старая школа Object.prototype.toString.call(val) кажется, также работает. В Node версии 11.12.0 он возвращает [object Generator] но последние Chrome и Firefox возвращают [object GeneratorFunction].

Так может быть так:

function isGenerator(val) {
    return /\[object Generator|GeneratorFunction\]/.test(Object.prototype.toString.call(val));

}