'check if function is a generator
I played with generators in Nodejs v0.11.2 and I'm wondering how I can check that argument to my function is generator function.
I found this way typeof f === 'function' && Object.getPrototypeOf(f) !== Object.getPrototypeOf(Function)
but I'm not sure if this is good (and working in future) way.
What is your opinion about this issue?
Solution 1:[1]
We talked about this in the TC39 face-to-face meetings and it is deliberate that we don't expose a way to detect whether a function is a generator or not. The reason is that any function can return an iterable object so it does not matter if it is a function or a generator function.
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++;
}
}
These two behave identical (minus .throw() but that can be added too)
Solution 2:[2]
In the latest version of nodejs (I verified with v0.11.12) you can check if the constructor name is equal to GeneratorFunction
. I don't know what version this came out in but it works.
function isGenerator(fn) {
return fn.constructor.name === 'GeneratorFunction';
}
Solution 3:[3]
this works in node and in firefox:
var GeneratorFunction = (function*(){yield undefined;}).constructor;
function* test() {
yield 1;
yield 2;
}
console.log(test instanceof GeneratorFunction); // true
But it does not work if you bind a generator, for example:
foo = test.bind(bar);
console.log(foo instanceof GeneratorFunction); // false
Solution 4:[4]
I'm using this:
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;
Solution 5:[5]
In node 7 you can instanceof
against the constructors to detect both generator functions and async functions:
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
This works for all circumstances in my tests. A comment above says it doesn't work for named generator function expressions but I'm unable to reproduce:
const genExprName=function*name(){};
genExprName instanceof GeneratorFunction; // true
(function*name2(){}) instanceof GeneratorFunction; // true
The only problem is the .constructor
property of instances can be changed. If someone was really determined to cause you problems they could break it:
// 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
Solution 6:[6]
TJ Holowaychuk's co
library has the best function for checking whether something is a generator function. Here is the source code:
function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
}
Reference: https://github.com/tj/co/blob/717b043371ba057cb7a4a2a4e47120d598116ed7/index.js#L221
Solution 7:[7]
As @Erik Arvidsson stated, there is no standard-way to check if a function is a generator function. But you can, for sure, just check for the interface, a generator function fulfills:
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
}
Thats's it.
Solution 8:[8]
function isGenerator(target) {
return target[Symbol.toStringTag] === 'GeneratorFunction';
}
or
function isGenerator(target) {
return Object.prototype.toString.call(target) === '[object GeneratorFunction]';
}
Solution 9:[9]
Mozilla javascript documentation describes Function.prototype.isGenerator
method MDN API. Nodejs does not seem to implement it. However if you are willing to limit your code to defining generators with function*
only (no returning iterable objects) you can augment it by adding it yourself with a forward compatibility check:
if (typeof Function.prototype.isGenerator == 'undefined') {
Function.prototype.isGenerator = function() {
return /^function\s*\*/.test(this.toString());
}
}
Solution 10:[10]
I checked how koa does it and they use this library: https://github.com/ljharb/is-generator-function.
You can use it like this
const isGeneratorFunction = require('is-generator-function');
if(isGeneratorFunction(f)) {
...
}
Solution 11:[11]
The old school Object.prototype.toString.call(val)
seems to work also. In Node version 11.12.0 it returns [object Generator]
but latest Chrome and Firefox return [object GeneratorFunction]
.
So could be like this:
function isGenerator(val) {
return /\[object Generator|GeneratorFunction\]/.test(Object.prototype.toString.call(val));
}
Solution 12:[12]
A difficulty not addressed on here yet is that if you use the bind
method on the generator function, it changes the name its prototype from 'GeneratorFunction' to 'Function'.
There's no neutral Reflect.bind
method, but you can get around this by resetting the prototype of the bound operation to that of the original operation.
For example:
const boundOperation = operation.bind(someContext, ...args)
console.log(boundOperation.constructor.name) // Function
Reflect.setPrototypeOf(boundOperation, operation)
console.log(boundOperation.constructor.name) // GeneratorFunction
Solution 13:[13]
By definition, a generator is simply a function that, when called, returns an iterator. So, I think you have only 2 methods that will always work:
1. Accept any function as a generator
2. Actually call the function and check if the result is an iterator
#2 may involve some overhead and if you insist on avoiding that overhead, you're stuck with #1. Fortunately, checking if something is an iterator is pretty simple:
if (object === undefined) || (object === null) {
return false
}
return typeof object[Symbol.iterator] == 'function'
FYI, that still doesn't guarantee that the generator will work OK since it's possible to create an object with the key Symbol.iterator that has a function value that does not, in fact, return that right type of thing (i.e. an object with value and done keys). I suppose you could check if the function has a next() method, but I wouldn't want to call that multiple times to see if all the return values have the correct structure ;-)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow