68

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?

Dima Vidmich
  • 1,335
  • 1
  • 11
  • 9
  • 2
    Pretty sure `f instanceof GeneratorFunction` should work, based on *15.19.3.1 The GeneratorFunction Constructor* of the current ES6 draft. –  May 26 '13 at 00:17
  • nodejs v0.11.2 has no GeneratorFunction so I think v8 v3.19.0 has no it also. but yes, this check will be much simpler. – Dima Vidmich May 26 '13 at 00:50
  • 2
    That constructor appears to be a new addition to the most recent draft. Searching the previous one, I don't find that text. Assuming it stays in the spec, I would imagine it would show up at some point. *EDIT:* ...ah yes, I see it in the change notes *"Added semantics for generator function and generator method definitions"* ...so looks like it just landed about 10 days ago. –  May 26 '13 at 00:53
  • 2
    I see this change was removed from v8 because of some test problems https://github.com/v8/v8/commit/a5d33556b2893a594a26c91fdc74986369be4ba0 – Dima Vidmich May 26 '13 at 01:08
  • There has to be something!!! a generator is different from a function.. – dansch Mar 13 '14 at 07:00
  • @T.J.Crowder if there is no different then there should be no difference when declaring them, from an IDEAL standpoint, though it would be very unfortunate for the initial release if they implement something that requires a difference in declaration but no meaningful difference afterwards. I'm not arguing, because practice is often not ideal, but there is no argument other than being a fanboy that you can make to say it SHOULDN'T have ability to read a difference when a difference it required to declare it. So please don't be a fanboy. – dansch Dec 06 '15 at 15:44
  • @Funkodebat: You're confusing the return value (an iterator) with the mechanism of creating the return value (a generator function vs. any of several other ways of creating iterators you might return from a non-generator function). And being non-constructive with the "fanboy" nonsense. I really should **not** have said "just a function returning an iterator" above, though, that's incorrect and I've removed it. But Erik's point remains valid: There's nothing useful you can do with the info other than say "Yep, that's a generator function." – T.J. Crowder Dec 06 '15 at 15:54
  • Since the question is specific to node v0.11.2, I don't know the answer. However, more recents versions can do well (also if bound) http://stackoverflow.com/questions/26769593/is-it-impossible-to-tell-if-a-function-is-a-generator-function-if-bind-has-be/36972162#36972162 – niry Sep 08 '16 at 06:15

13 Answers13

59

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)

azz
  • 5,852
  • 3
  • 30
  • 58
Erik Arvidsson
  • 915
  • 9
  • 10
  • 5
    Wow... too bad :( Not ability to determine is it generator function or simple function will not allow nice things, like integration with primise libraries (like Q.async) to automatically detect generators and fetch/push values there to have nice and clean "primise" api based on generators. – Valentyn Shybanov Nov 07 '13 at 13:43
  • 2
    @Erik Arvidsson Where might we find a documentation for Symbol function? – Lex Podgorny Jan 07 '14 at 17:00
  • 3
    I have to note that, even with the most recent dev version of Node.js, this snippet does not work and I get a `Unexpected token [` at `[iterator]: function() {`. Where does that come from? – Yanick Rochon Jan 07 '14 at 20:29
  • @LexPodgorny I found very little mention of Symbol, [here](http://wingolog.org/archives/2013/10/07/es6-generators-and-iteration-in-spidermonkey), [here](http://domenic.me/2013/09/06/es6-iterators-generators-and-iterables/#toc_4) and [here](http://tc39wiki.calculist.org/es6/symbols/details/) – Some Guy Mar 13 '14 at 08:25
  • @LexPodgorny There actually seems to be a lot more if you Google "ES6 Symbols". They were previously known as `Names`. Also, a better [resource here](https://github.com/dherman/tc39-codex-wiki/blob/master/data/es6/symbols/index.md#symbols) – Some Guy Mar 13 '14 at 08:36
  • @YanickRochon it's new object notation, you can construct an object and use bracket notation - that is `var obj = {}; obj.next = function(){...}; obj[iterator] = () => this;` – Benjamin Gruenbaum Sep 30 '14 at 06:58
  • 1
    @Erik, So you are saying a generator function is just a special class of function, not something different? Then maybe we can see if a function is a generator by checking that it has all the characteristics of a generator (returns object containing `next` and `[iterator]`, `next` returns `value` and `count`, etc.) Would this consistently work for the foreseeable future? – trysis Aug 23 '15 at 23:21
  • By the way, wow, is that really all that separates a function from a generator function, or is that just a simplified picture? – trysis Aug 23 '15 at 23:22
  • @Erik What about with async functions. Those defer things to following ticks, so I think it might make sense to know if we have an `async` function or not in certain cases. Just because any function can return a Promise doesn't mean we shouldn't be able to tell the difference. – trusktr Jan 12 '16 at 09:07
  • Another way of phrasing this answer: a generator is a function which returns an iterable. – cdosborn Jul 20 '17 at 04:38
53

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';
}
Boopathi Rajaa
  • 4,659
  • 2
  • 31
  • 53
smitt04
  • 3,018
  • 2
  • 28
  • 30
  • "You can use obj.constructor.name to check the "class" of an object" https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name, though with caveats, see http://stackoverflow.com/questions/29310530/get-the-class-name-of-es6-class-instance – user5321531 Apr 01 '15 at 18:22
  • 2
    This only works with function declarations and anonymous functions, it does not work with named function expressions. – trysis Aug 23 '15 at 23:16
  • 5
    Great solution, thanks! Updated for today's JS: `const isGenerator = fn => ['GeneratorFunction', 'AsyncGeneratorFunction'].includes(fn.constructor.name)`. Async generators are part of ES2018, available in node v10, see [node.green](https://node.green/#ES2018-features-Asynchronous-Iterators-async-generators) – tleb Aug 03 '18 at 09:52
  • See the accepted answer: "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." Your solution is an abuse and implementation specific. – snnsnn Jun 15 '23 at 03:35
15

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

jsfiddle

But it does not work if you bind a generator, for example:

foo = test.bind(bar); 
console.log(foo instanceof GeneratorFunction); // false
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Nick Sotiros
  • 2,194
  • 1
  • 22
  • 18
9

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;
Albert
  • 65,406
  • 61
  • 242
  • 386
  • 2
    I shorten this to `Generator = (function*(){}).constructor; g instanceof Generator`, unfortunatly `(function*(){}).prototype.constructor` is not a valid parameter of instanceof for checking for generator iterators – Nick Sotiros May 09 '14 at 16:41
7

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
  • Worked for me. Great thinking! Of course, there's always Nick Sotiros' answer, 2 years before you. – MasterBob Jan 06 '17 at 03:19
  • 1
    This answer fails if you use an async generator function, only 1 true is returned: `async function*asgen(){}` – Ferrybig Jan 23 '18 at 10:51
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

freddyrangel
  • 1,353
  • 1
  • 11
  • 20
5

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.

kyr0
  • 341
  • 4
  • 6
  • I would have checked `fn.constructor.name` but since the function was passed throught a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) it reported it as a regular function... So i had to do what you suggested and apply a coroutine afterwards – Endless Dec 12 '17 at 01:28
  • 2
    If it Symbol.iterator's like a duck, next's like a duck, and throw's like a duck, then.... – Seth Dec 16 '20 at 16:39
3

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));

}
Mario Škrlec
  • 305
  • 4
  • 7
3
function isGenerator(target) {
  return target[Symbol.toStringTag] === 'GeneratorFunction';
}

or

function isGenerator(target) {
  return Object.prototype.toString.call(target) === '[object GeneratorFunction]';
}
monochromer
  • 31
  • 1
  • 2
2

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());
    }
}
Lex Podgorny
  • 2,598
  • 1
  • 23
  • 40
  • 2
    you may want to consider the whitespace that could be there. `function *(args) {}` or `function* (args){}` I've seen both. I wouldn't be surprised if node added a detector natively because toString is too expensive – dansch Mar 13 '14 at 06:56
2

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)) {
    ...
}
kraf
  • 1,269
  • 12
  • 21
  • I will add a line of code to demonstrate the usefulness of the library, but I still think mentioning a reusable library that solves the stated problem makes sense here. – kraf Jun 07 '17 at 13:08
1

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 ;-)

John Deighan
  • 4,329
  • 4
  • 18
  • 19
0

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
Lorenzo
  • 31
  • 2