我在 Nodejs v0.11.2 中玩过生成器,我想知道 我如何检查我的函数的参数是生成器函数。
我发现了这种方式
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.12验证)中,您可以检查构造函数名称是否等于
GeneratorFunction
。我不知道这是哪个版本,但它有效。
function isGenerator(fn) {
return fn.constructor.name === 'GeneratorFunction';
}
这适用于 Node 和 Firefox:
var GeneratorFunction = (function*(){yield undefined;}).constructor;
function* test() {
yield 1;
yield 2;
}
console.log(test instanceof GeneratorFunction); // true
我正在用这个:
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;
在节点 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
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
正如 @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
}
就是这样。
老派
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]';
}
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)) {
...
}
根据定义,生成器只是一个函数,在调用时返回一个迭代器。所以,我认为只有两种方法永远有效:
1. Accept any function as a generator
2. Actually call the function and check if the result is an iterator
#2 可能会涉及一些开销,如果您坚持避免这种开销,那么您将陷入#1。幸运的是,检查某物是否是迭代器非常简单:
if (object === undefined) || (object === null) {
return false
}
return typeof object[Symbol.iterator] == 'function'
仅供参考,这仍然不能保证生成器能够正常工作,因为可以使用键 Symbol.iterator 创建一个对象,该对象的函数值实际上不会返回正确类型的事物(即对象带有值和完成键)。我想您可以检查该函数是否有 next() 方法,但我不想多次调用该方法来查看所有返回值是否具有正确的结构;-)
这里尚未解决的一个困难是,如果您在生成器函数上使用
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