Проверьте, является ли функция генератором
Я играл с генераторами в Nodejs v0.11.2, и мне интересно
как я могу проверить, что аргумент моей функции является функцией генератора.
Я нашел этот способ typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)
, но я не уверен, что это хорошо (и работает в будущем).
Каково ваше мнение об этой проблеме?
Ответы
Ответ 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));
}