How to tell function and generator function apart?

# Guilherme Souza (10 years ago)

I was wondering how one could check if a given function is a generator function, is a cross-realm way:

let f = function(){}
let g = function*(){}

typeof f === typeof g // 'function'

f instanceof Function // true, but only in the same Realm
g instanceof Function // true, but again only in the same Realm

let GeneratorFunction = Object.getPrototypeOf(function*(){}).constructor
f instanceof GeneratorFunction // false
g instanceof GeneratorFunction // true, but only in the same Realm

// This works, but relies on Function.prototype being a function itself.
typeof Object.getPrototypeOf(f) // 'function'
typeof Object.getPrototypeOf(g) // 'object'

So what is the recommended way to do this type of check? Would something like Array.isArray for generator functions be helpful?

, Gui

# Claude Pache (10 years ago)

Here is how to get your answer:

g.toString().startsWith("function*")

Since being a generator function (in contrast with being a function that may return an iterator) is a question of implementation rather of behaviour, I think that peeking at the source code using .toString() is the right way to do it.

# Damian Senn (10 years ago)

On 03/03/2015 05:41 PM, Guilherme Souza wrote:

Hi all,

I was wondering how one could check if a given function is a generator function, is a cross-realm way:

I'm detecting this with the following piece of code:

if ((function*(){}).constructor.name == 'GeneratorFunction')) { // is generator function }

or something like:

function isGenerator(fn) { return fn && fn.constructor && fn.constructor.name == 'GeneratorFunction' }

Damian

# Jordan Harband (10 years ago)

It's not quite that simple due to variations across different engines, but I've got a module www.npmjs.com/package/is-generator-function that covers it.

# Erik Arvidsson (10 years ago)

Why do you want to do this?

It is an antipattern that we have covered before.

# Dean Landolt (10 years ago)

One use case is for coroutine runners which accept either generators or generator functions:

deanlandolt/copromise/blob/master/copromise.js#L34-L36

Is there a better way to accomplish this?

# Guilherme Souza (10 years ago)

My use case is similar to what Dean has showed:

My run() lib abstraction should work with:

  • a function, with node's done() callback style
  • a generator function, with co's yield promise style [1]
// ES5 function
run(function (data, done) {
    doSomeAsyncJob(data, done)
})

// Generator function
run(function* (data) {
    return yield getSomeAsyncJobPromise(data)
})

[1] www.npmjs.com/package/co

# Domenic Denicola (10 years ago)

As discussed previously such overloading is pretty bad. If someone creates a generator-returning function manually, instead of using a generator function to do so, they will start getting unexpected results from your library. You might think this is rare but it's actually pretty reasonable---consider e.g. yourLibrary(transform(function* () { ... })) where transform is a higher-order function which manually creates a generator based on its argument.

An analogy would be that your and Dean's functions are similar to something like

function getFullName(person) {
  if (person.constructor !== Person) {
    // Do something else, e.g. `return person.toString()`
  }

  return `${person.firstName} ${person.lastName}`;
}

That is, now you have prohibited people from using { firstName, lastName } object literals, Person subclass instances, Dog instances, etc. That's very unfortunate practice. The same goes for generators.

# Benjamin Gruenbaum (10 years ago)

Yes, don't make the distinction and instead run only promises - provide a function that takes a sequence and returns a promise over it. Not every iterable sequence is a sequence of async values.

# Dean Landolt (10 years ago)

On Tue, Mar 3, 2015 at 5:26 PM, Domenic Denicola <d at domenic.me> wrote:

As discussed previously such overloading is pretty bad. If someone creates a generator-returning function manually, instead of using a generator function to do so, they will start getting unexpected results from your library. You might think this is rare but it's actually pretty reasonable---consider e.g. yourLibrary(transform(function* () { ... })) where transform is a higher-order function which manually creates a generator based on its argument.

An analogy would be that your and Dean's functions are similar to something like

function getFullName(person) {
  if (person.constructor !== Person) {
    // Do something else, e.g. `return person.toString()`
  }

  return `${person.firstName} ${person.lastName}`;
}

That is, now you have prohibited people from using { firstName, lastName } object literals, Person subclass instances, Dog instances, etc. That's very unfortunate practice. The same goes for generators.

FWIW the coroutine runner in my example is strictly a runner, not a wrapper returning something analogous to an async function. I definitely see the benefit of such a wrapper (and have been meaning to get around to adding one), but the example I'd linked to is more like running coroutine "program". I suspect there's still some benefit to this kind of style, if just for simple debug cases, but with the presence of some kind of async function wrapper, this "program" wrapper would really only need to support generator functions, which means no need to bother w/ any isGeneratorFunction nonsense.

# Domenic Denicola (10 years ago)

That's not really the issue at hand, though. Your runner won't work with functions that manually return generator instances, due to its type-testing. Even if those functions return generator instances express a perfectly valid async program sequence.

# Dean Landolt (10 years ago)

On Tue, Mar 3, 2015 at 10:00 PM, Domenic Denicola <d at domenic.me> wrote:

That's not really the issue at hand, though. Your runner won't work with functions that manually return generator instances, due to its type-testing. Even if those functions return generator instances express a perfectly valid async program sequence.

I don't follow -- it definitely used to work (I use the past tense because I went ahead and converted to do the async fn wrapper thing instead). The type test was just to determine whether or not to flatten generator functions into generator instances, which is what the coro trampoline expects. Any generator instance can be run through that coro tramp -- even ones not designed as coroutines (not recommended, of course -- though it'd be fine so long as it terminates).

I just noticed the coro tramp in the async/await spec: lukehoban/ecmascript-asyncawait#spawning. This is just about identical to my own, though I can't quite figure out why it bothers operating on generator functions rather than instances. It seems more natural to instantiate the generators (and their argument bindings) from within an async fn wrapper, e.g. deanlandolt/copromise/blob/master/copromise.js#L23.

Removing the type test definitely cleaned up the API, and managed to shrink an already tiny code base even more, so it was clearly a win. But I still can't see how how type testing would have broken anything at all in the previous design.

# Claude Pache (10 years ago)

Le 3 mars 2015 à 23:19, Guilherme Souza <19gui94 at gmail.com> a écrit :

My use case is similar to what Dean has showed:

My run() lib abstraction should work with:

  • a function, with node's done() callback style
  • a generator function, with co's yield promise style [1]
// ES5 function
run(function (data, done) {
    doSomeAsyncJob(data, done)
})

// Generator function
run(function* (data) {
    return yield getSomeAsyncJobPromise(data)
})

[1] www.npmjs.com/package/co, www.npmjs.com/package/co

The hard part is that you should take the "generator function" path for functions that behave like generator functions even if they are not, e.g., gen.bind(null, 'foo'), where gen is a true generator function.

This is how I'd consider to do, if I really couldn't ask the user about the nature of the function.

  1. At first, don't make any assumption about which code path to take. Let f be the function to work with;
  2. As a common part of the algorithms for the two cases, call f(data, done), assuming that it is equivalent to f(data) if f is a generator function;
  3. Check whether the result is iterable, i.e, if typeof result[Symbol.iterator] === 'function'. If yes, proceed with the "generator function" path. If no, proceed with the "function-with-callback" path.