check if function is a generator
Asked Answered
H

13

69

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?

Hindquarter answered 25/5, 2013 at 23:50 Comment(8)
Pretty sure f instanceof GeneratorFunction should work, based on 15.19.3.1 The GeneratorFunction Constructor of the current ES6 draft.Lark
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.Hindquarter
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.Lark
I see this change was removed from v8 because of some test problems github.com/v8/v8/commit/…Hindquarter
There has to be something!!! a generator is different from a function..Tracytrade
@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.Tracytrade
@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."Larghetto
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) #26770093Nonaligned
P
60

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)

Pangaro answered 29/10, 2013 at 14:0 Comment(10)
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.Impasse
@Erik Arvidsson Where might we find a documentation for Symbol function?Slocum
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?Kobarid
@LexPodgorny I found very little mention of Symbol, here, here and hereLaconic
@LexPodgorny There actually seems to be a lot more if you Google "ES6 Symbols". They were previously known as Names. Also, a better resource hereLaconic
@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;Blond
@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?Parentage
By the way, wow, is that really all that separates a function from a generator function, or is that just a simplified picture?Parentage
@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.Fribourg
Another way of phrasing this answer: a generator is a function which returns an iterable.Chitkara
S
54

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';
}
Sibie answered 24/3, 2014 at 13:11 Comment(4)
"You can use obj.constructor.name to check the "class" of an object" developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…, though with caveats, see #29311030Amide
This only works with function declarations and anonymous functions, it does not work with named function expressions.Parentage
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.greenSnyder
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.Swashbuckler
E
17

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

Ebby answered 23/7, 2014 at 3:32 Comment(2)
For me, in Chromium 76 and node 10, the bound generator works too.Tupper
Can confirm that as of 2024, the bound generator function is detected with instanceof GeneratorFunction in both Chrome and Firefox. Will submit an edit to this answer now.Genetics
L
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;
Licko answered 21/3, 2014 at 8:44 Comment(1)
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 iteratorsEbby
T
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
Twentieth answered 20/11, 2016 at 13:1 Comment(2)
Worked for me. Great thinking! Of course, there's always Nick Sotiros' answer, 2 years before you.Coad
This answer fails if you use an async generator function, only 1 true is returned: async function*asgen(){}Kinglet
B
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

Brett answered 16/6, 2016 at 16:55 Comment(0)
C
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.

Circulate answered 7/6, 2017 at 12:53 Comment(2)
I would have checked fn.constructor.name but since the function was passed throught a Proxy it reported it as a regular function... So i had to do what you suggested and apply a coroutine afterwardsSomme
If it Symbol.iterator's like a duck, next's like a duck, and throw's like a duck, then....Reflex
A
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));

}
Anatolic answered 18/5, 2019 at 10:53 Comment(0)
S
3
function isGenerator(target) {
  return target[Symbol.toStringTag] === 'GeneratorFunction';
}

or

function isGenerator(target) {
  return Object.prototype.toString.call(target) === '[object GeneratorFunction]';
}
Sallie answered 24/6, 2020 at 8:19 Comment(0)
S
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());
    }
}
Slocum answered 7/1, 2014 at 16:41 Comment(1)
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 expensiveTracytrade
P
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)) {
    ...
}
Potaufeu answered 7/6, 2017 at 11:33 Comment(1)
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.Potaufeu
W
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 ;-)

Witt answered 25/4, 2022 at 14:3 Comment(0)
D
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
Daugava answered 9/4, 2017 at 16:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.