How to tell function and generator function apart?
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.
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
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.
Why do you want to do this?
It is an antipattern that we have covered before.
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?
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)
})
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.
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.
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* () { ... }))
wheretransform
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.
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.
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.
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) })
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.
- At first, don't make any assumption about which code path to take. Let
f
be the function to work with; - As a common part of the algorithms for the two cases, call
f(data, done)
, assuming that it is equivalent tof(data)
iff
is a generator function; - 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.
I was wondering how one could check if a given function is a generator function, is a cross-realm way:
So what is the recommended way to do this type of check? Would something like
Array.isArray
for generator functions be helpful?, Gui