检查函数是否是生成器

我在Nodejs v0.11.2中使用生成器,我想知道如何检查我的函数的参数是生成器函数。

我find了这样的方式typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)但我不知道这是否是好的(并在未来工作)的方式。

你对这个问题有什么看法?

我们在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(),但也可以添加)

在最新版本的nodejs(我用v0.11.12validation过)中,你可以检查构造函数的名字是否等于GeneratorFunction 。 我不知道这是什么版本出来,但它的工作原理。

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

我正在使用这个:

 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; 

TJ Holowaychuk的co库具有检查是否有发生器function的最佳function。 这里是源代码:

 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

这在节点和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 

在节点7中,您可以针对构造函数使用instanceof来检测生成器函数和asynchronous函数:

 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 

这适用于我的testing中的所有情况。 上面的评论说,它不适用于命名的生成器函数expression式,但我无法重现:

 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 

Mozilla javascript文档介绍了Function.prototype.isGenerator方法的MDN API 。 Nodejs似乎并没有实现它。 但是,如果你愿意限制你的代码只用function*来定义生成器(不返回可迭代对象),你可以通过向前兼容性检查来增加它:

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

我检查了koa是如何做的,他们使用这个库: https : //github.com/ljharb/is-generator-function 。

你可以像这样使用它

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

这里没有解决的一个困难是,如果你在生成器函数上使用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 

正如@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's safe to assume the function is a generator function or a shim that behaves like a generator function let nextValue = fibonacciGenerator.next().value; // 5 } 

就是这样。

Interesting Posts