7

Whats a reliable way to check if a function is a generator, e.g.:

let fn = function* () {
    yield 100;
}

if (fn instanceof ??) {
   for (let value in fn()) {
       ...
   }
}

The only way I can think of is fn.toString().startsWith('function*') but that's extremely hacky and unreliable

context: nodejs 4+

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
Chris Barrett
  • 516
  • 3
  • 12
  • Maybe you could check if `next` is defined on it? – Hunan Rostomyan Dec 05 '15 at 08:57
  • Just tried, typeof fn.next is "undefined" – Chris Barrett Dec 05 '15 at 08:58
  • You could probably do something like `if(typeof fn().next === 'function') {}` – Saad Dec 05 '15 at 09:00
  • 1
    That's not an *iterator* function, that's a *generator* function. – T.J. Crowder Dec 05 '15 at 09:01
  • @saadq: But that would mis-identify any function returning an object with a `next` method. – T.J. Crowder Dec 05 '15 at 09:10
  • check `fn.constructor.name == "GeneratorFunction"` – Jaromanda X Dec 05 '15 at 09:26
  • Yep, it's definitely not a good way to do it. That was just meant to explain that he was supposed to be doing `next()` instead of `next`. – Saad Dec 05 '15 at 09:26
  • @JaromandaX: It's a good thought, esp. for the contained environment (e.g., just has to work for V8). **But**, sadly, A) People mess up the `constructor` property all the time, you can't rely on it for basically anything if you're testing a function that isn't your own code. And B) It's possible to have a function called `GeneratorFunction`, which would lead to mis-identification... – T.J. Crowder Dec 05 '15 at 09:30
  • what about the added test that `fn().next.prototype === undefined` – Jaromanda X Dec 05 '15 at 09:45
  • @JaromandaX: All arrow functions have `.prototype === undefined`, so if `fn` returns an object with a `next` method that's an arrow function... Separately, that check would require calling the function in order to find out what it is. – T.J. Crowder Dec 05 '15 at 10:23
  • @bergi: Nice catch on it being a dupe, I frankly didn't think to look, given how relatively new this stuff is. – T.J. Crowder Dec 05 '15 at 15:22
  • @T.J.Crowder: Yeah, the dupe is only over 2 years old :-) We've got many questions on how to distinguish all the new ES6 function types already, [this one](http://stackoverflow.com/q/31936822/1048572) might become a suitable canonical maybe (with some work) – Bergi Dec 05 '15 at 15:26
  • @ChrisBarrett: I've updated my answer, there *is* a better way -- and yet, the check probably doesn't tell you anything meaningful. – T.J. Crowder Dec 05 '15 at 16:12

1 Answers1

11

Erik Arvidsson makes a good point in this answer to an earlier version of this question (it didn't occur to me that it's a dupe), that since any function can return an iterator, there's little point in checking whether a function is a generator or not. That is, there's not much you can do with the information in practical terms, since non-generators can return iterators.


I was wrong before, there is a better way than your toString check (if for some reason you have a valid need to do it at all):

  1. (Once) Get the value of the default constructor of a generator function, which is specified here. It doesn't have a global like Function and such do.

  2. (Whenever you need to check) Check to see if your target function is instanceof that generator function constructor.

E.g.:

// Once
var GeneratorFunction = (function*(){}).constructor;

// Whenever you need to check
if (fn instanceof GeneratorFunction) {
    // Yep, it's a generator function
}

Old things ruled out:

I don't see anything in the specification that lets us directly access the [[FunctionKind]] internal slot.

The spec does say:

Unlike function instances, the object that is the value of the a GeneratorFunction’s prototype property does not have a constructor property whose value is the GeneratorFunction instance.

So in theory:

if (!fn.prototype.hasOwnProperty("constructor")) {
    // It's a generator function
}

but, that would be incredibly unreliable, as people do things like this all the time (although hopefully less so as people start using class):

function Foo() {
}
Foo.prototype = {
    method: function() {}
};

While that Foo.prototype object has a constructor property, it's inherited, not "own". We could do an in check, or a .constructor == fn check, but the above would still mis-identify it. You just can't trust constructor in the wild, people mess it up too much.

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • `toString` is horrible. Please don't recommend this. There are much better ways, such as `instanceof Generator` (where `Generator` is the builtin constructor of generator functions). – Bergi Dec 05 '15 at 15:19
  • And in any case, [you shouldn't be doing this anyway](http://stackoverflow.com/a/19660350/1048572), it makes no sense to distinguish a generator function from any other (normal) function that returns an iterator (or generator). – Bergi Dec 05 '15 at 15:20
  • 1
    @Bergi: I didn't recommend it. I answered the question, which was *how to*, not *should you*. – T.J. Crowder Dec 05 '15 at 15:21
  • @Bergi: Where is the `Generator` symbol defined by the spec? V8 doesn't know it. – T.J. Crowder Dec 05 '15 at 15:24
  • I see it's no recommendation, I just thought to add a warning would be appropriate. My first comment was about your statement "*I don't think there's a better way than a beefed-up version of `toString`*" which I think is wrong. – Bergi Dec 05 '15 at 15:28
  • `Generator` was not made a global variable for some reason (not to clutter the scope, probably), but you [can still access it](http://stackoverflow.com/a/24901386/1048572) – Bergi Dec 05 '15 at 15:29
  • 1
    It's not an instance property (like `.prototype` is - generator functions are "constructor functions"), it's a [property inherited from `GeneratorFunction.prototype`](http://www.ecma-international.org/ecma-262/6.0/#sec-generatorfunction.prototype.constructor). The spec is confusing :-) – Bergi Dec 05 '15 at 16:01
  • @Bergi: Got it. Yeesh! – T.J. Crowder Dec 05 '15 at 16:06