我在 Nodejs v0.11.2 中使用了生成器,我想知道如何检查我的函数的参数是生成器函数。
我找到了这种方式typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)
,但我不确定这是否是好的(并且在未来工作)方式。
你对这个问题有什么看法?
我在 Nodejs v0.11.2 中使用了生成器,我想知道如何检查我的函数的参数是生成器函数。
我找到了这种方式typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)
,但我不确定这是否是好的(并且在未来工作)方式。
你对这个问题有什么看法?
在最新版本的 nodejs(我用 v0.11.12 验证)中,您可以检查构造函数名称是否等于GeneratorFunction
. 我不知道这是什么版本,但它可以工作。
function isGenerator(fn) {
return fn.constructor.name === 'GeneratorFunction';
}
我们在 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() 但也可以添加)
这适用于节点和Firefox:
var GeneratorFunction = (function*(){yield undefined;}).constructor;
function* test() {
yield 1;
yield 2;
}
console.log(test instanceof GeneratorFunction); // true
但是如果你绑定一个生成器,它就不起作用,例如:
foo = test.bind(bar);
console.log(foo instanceof GeneratorFunction); // false
我正在使用这个:
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 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
在节点 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
正如@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
}
就是这样。
Mozilla javascript 文档描述了MDN APIFunction.prototype.isGenerator
方法。Nodejs 似乎没有实现它。但是,如果您愿意将代码限制为仅使用(不返回可迭代对象)定义生成器,则可以通过自己添加前向兼容性检查来增加它:function*
if (typeof Function.prototype.isGenerator == 'undefined') {
Function.prototype.isGenerator = function() {
return /^function\s*\*/.test(this.toString());
}
}
老学校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));
}
function isGenerator(target) {
return target[Symbol.toStringTag] === 'GeneratorFunction';
}
或者
function isGenerator(target) {
return Object.prototype.toString.call(target) === '[object GeneratorFunction]';
}
这里尚未解决的一个困难是,如果您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