check if function is a generator

JavascriptGeneratorYieldEcmascript 6

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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

Solution 4 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

function isGenerator(target) {
  return target[Symbol.toStringTag] === 'GeneratorFunction';
}

or

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

Solution 9 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDima VidmichView Question on Stackoverflow
Solution 1 - JavascriptErik ArvidssonView Answer on Stackoverflow
Solution 2 - Javascriptsmitt04View Answer on Stackoverflow
Solution 3 - JavascriptNick SotirosView Answer on Stackoverflow
Solution 4 - JavascriptAlbertView Answer on Stackoverflow
Solution 5 - Javascriptuser6798019View Answer on Stackoverflow
Solution 6 - JavascriptfreddyrangelView Answer on Stackoverflow
Solution 7 - Javascriptkyr0View Answer on Stackoverflow
Solution 8 - JavascriptmonochromerView Answer on Stackoverflow
Solution 9 - JavascriptLexView Answer on Stackoverflow
Solution 10 - JavascriptkrafView Answer on Stackoverflow
Solution 11 - JavascriptMario ŠkrlecView Answer on Stackoverflow
Solution 12 - JavascriptLorenzoView Answer on Stackoverflow
Solution 13 - JavascriptJohn DeighanView Answer on Stackoverflow