Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Programmatically check if function is async #78

Closed
christianalfoni opened this issue Dec 14, 2015 · 32 comments
Closed

Programmatically check if function is async #78

christianalfoni opened this issue Dec 14, 2015 · 32 comments

Comments

@christianalfoni
Copy link

Maybe this is part of the spec, but I was not able to find it in the text.

Will it be possible to check if a function is an async function? This would be beneficial developing libraries around async functions. Just to make my point clear with pseudo code:

async myFunc() {

}

myFync.isAsync // true
@inikulin
Copy link

function isAsync(fn) {
   return fn.constructor.name === 'AsyncFunction';
}

See: https://tc39.github.io/ecmascript-asyncawait/#async-function-constructor-properties

@domenic
Copy link
Member

domenic commented Dec 14, 2015

Please do not do this. Any function that returns a promise is an async function.

@inikulin
Copy link

@domenic As understand the issue is that you can't determine return value ahead of time.

@inikulin
Copy link

And

Any function that returns a promise is an async function.

in general, it's not quite valid statement, e.g.:

function foo(bar) {
  if (bar)
    return new Promise(resolve => ...);

   // NOTE: please, never do this!
   return null;
}

@qfox
Copy link

qfox commented Dec 14, 2015

async function x() {}
function y() { return Promise.resolve(); }
function z() { return y() || x(); }

Good luck!

@domenic
Copy link
Member

domenic commented Dec 14, 2015

Yes, @zxqfox has illustrated the issue precisely. There is no behavior difference between his x, y, and z. Every library that develops around async functions should allow both x, y, and z to be used as an async function. If it does not, it is a bad library.

In JavaScript, this kind of type checking is in general an antipattern. You should instead proceed by assuming that what you are given is the correct type. Your program will fail, correctly, if it is not.

@christianalfoni
Copy link
Author

Yeah, my point is to understand how the function will run ahead of calling it. In theory you can have a library that handles passed async functions differently than normal functions. For example passing different arguments to them when they are called, for whatever reason. As I understand async functions always returns a promise? It is a nice guarantee that is only valuable if you can actually check for it.

Historically things that are considered bad practice can suddenly become useful when devs get creative. Like Google with Angular. Use of Eval for mixing html attributes with javascript and toString of function name for dependency injection are at its core really bad practices, and personally I think it is crazy, but it has served a purpose :-)

Anyways, just saying that if it is not a technical reason, but because it is considered a bad practice today, maybe it will limit some good (or bad) idea worth exploring later on.

@domenic
Copy link
Member

domenic commented Dec 14, 2015

It is bad practice for a technical reason, as explained above. There is no difference between async functions and other promise returning functions, so libraries that try to distinguish them are technically unsound.

@christianalfoni
Copy link
Author

Okay, cool, thanks for taking the time to answer :-)

@inikulin
Copy link

@domenic If it's good practice is there any reason to distinguish async functions with different ctor type? AFAIK arrow's ctor.name is same as for the regular functions, despite context-related differentiation. So, maybe it would be better to apply same practice to async functions. Just curious.

@domenic
Copy link
Member

domenic commented Dec 15, 2015

@inikulin it's so that you can do new AsyncFunction("string of code here") and the string of code is allowed to use the await keyword.

@inikulin
Copy link

@domenic Right. Thank you for the clarification.

@inikulin
Copy link

@domenic If I get it right from the TC discussion AsyncFunction ctor will not be exposed to the end user, right?

@ljharb
Copy link
Member

ljharb commented Dec 24, 2015

@inikulin correct, just like GeneratorFunction - however, both are accessible via

const GeneratorFunction = (function* () {}).constructor;
const AsyncFunction = (async function () {}).constructor;

@benjamingr
Copy link

Hey, just ran into this issue:

Please do not do this. Any function that returns a promise is an async function.

I currently have a use case where I need to serialize functions in order to send them to run on another VM instance - it's the first time I need such a distinction to be fair.

@matthiasg
Copy link

I am doing something similar generating new code to replicate an existing class dynamically (written by a user). The code does not run, so whether or not it returns a Promise is irrelevant, what matters is whether the await keyword is supported inside it.

@fatcerberus
Copy link

Please do not do this. Any function that returns a promise is an async function.

In general I agree with this, however there are some valid use cases, e.g. see: spheredev/neosphere#251

async function cannot be called with new (it throws a TypeError) so I need to be able to detect whether that's the case and adjust accordingly. The return value of the function is irrelevant in this case, I only care whether or not I can call it with new.

@ljharb
Copy link
Member

ljharb commented Feb 18, 2018

@fatcerberus neither can arrow functions. Try checking the .prototype property?

@fatcerberus
Copy link

Indeed, but I don't expect anyone to export an arrow function as the main entry point (and if they do I'm fine with it not working). 😄 Same goes for generator functions.

In any case I already know I can check the constructor property and/or prototype to detect asyncs, I just posted above to point out what I thought was a valid use case for this.

@benjamingr
Copy link

The return value of the function is irrelevant in this case, I only care whether or not I can call it with new.

Try calling it with new and handle the exception?

@fatcerberus
Copy link

Try calling it with new and handle the exception?

No good, if the exception turns out to originate within the function code itself you've now caused unwanted side effects. I guess you could compare the error messages to decide whether or not to retry but that seems like a really ugly hack just begging for regressions. Testing fn.constructor.name ahead of time is much cleaner.

@domenic
Copy link
Member

domenic commented Feb 18, 2018

You should not use async function as a proxy for "can be called with new". Use an actual isConstructor check, e.g. https://esdiscuss.org/topic/isconstructor#content-11.

@fatcerberus
Copy link

@domenic Thanks, wasn’t aware there was a side-effect free way to do that without explicit support in the JS-engine’s API.

@trusktr
Copy link

trusktr commented Mar 26, 2018

In JavaScript, this kind of type checking is in general an antipattern

Maybe, but that doesn't answer the question. It only detracts from it.

It is perfectly valid for some API to say "if you pass an async function, this tool will do such and such, otherwise it will do such and such".

I, for example, and making an ES5-based class inheritance tool, and I'd like to throw an error if the user provides an async function constructor (which is possible in ES5 where ctors can be called as ctor.call(this)). It's a perfectly valid use case for what I'm doing.

@ljharb
Copy link
Member

ljharb commented Mar 26, 2018

@trusktr your only options are to extract the constructor and use instanceof (which isn’t cross-realm-safe), or use Function.prototype.toString.call - but as is pointed out above, you’d need a Proxy for a true “is constructor” check.

@zhujun24
Copy link

zhujun24 commented Jul 3, 2018

asyncFunc.toString()

const simpleFunc = () => {}
const asyncFunc = async () => {}
console.log(~simpleFunc.toString().indexOf(' asyncFunc()')) // 0
console.log(~asyncFunc.toString().indexOf(' asyncFunc()')) // -9

@ljharb
Copy link
Member

ljharb commented Jul 3, 2018

@zhujun24 /* asyncFunc() */ () => { /* asyncFunc() */ } it’s more complex than indexOf, I’m afraid - there’s lots of ways a function can be written.

@zhujun24
Copy link

zhujun24 commented Jul 4, 2018

const simpleFunc = () => {}
const asyncFunc = () => {}
console.log(~simpleFunc.toString().indexOf(' asyncFunc()')) // -9
console.log(~asyncFunc.toString().indexOf(' asyncFunc()')) // -9

@ljharb It has nothing to do with function name

@fatcerberus
Copy link

But... Your indexOf check is specifically looking for the position of the function name...

@zhujun24
Copy link

zhujun24 commented Jul 4, 2018

Oooh. Function.prototype.toString() is a imperfect way to detect async function.

@ljharb
Copy link
Member

ljharb commented Jul 4, 2018

@zhujun24 indeed, as has been noted up thread, there is no good way to detect one.

@MarkEhr
Copy link

MarkEhr commented Apr 9, 2020

I stumbled upon this trying to execute an uknown function received as a parameter. How I finally solved it was forcing it to be async so I could always execute it with await.
Something like:

async function executer(hisFunc){
  const result = await new Promise(()=>{
    return hisFunc(2);
  });
  /* Do stuff with result */
}

It may help someone

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests