I have to pass a function to another function, and execute it as a callback. The problem is that sometimes this function is async, like:
async function() {
Native async
functions may be identifiable when being converted to strings:
asyncFn[Symbol.toStringTag] === 'AsyncFunction'
Or by AsyncFunction constructor:
const AsyncFunction = (async () => {}).constructor;
asyncFn instanceof AsyncFunction === true
This won't work with Babel/TypeScript output, because asyncFn
is regular function in transpiled code, it is an instance of Function
or GeneratorFunction
, not AsyncFunction
. To make sure that it won't give false positives for generator and regular functions in transpiled code:
const AsyncFunction = (async () => {}).constructor;
const GeneratorFunction = (function* () => {}).constructor;
(asyncFn instanceof AsyncFunction && AsyncFunction !== Function && AsyncFunction !== GeneratorFunction) === true
Since native async
functions were officially introduced to Node.js in 2017, the question likely refers to Babel implementation of async
function, which relies on transform-async-to-generator to transpile async
to generator functions, may also use transform-regenerator to transpile generator to regular functions.
The result of async
function call is a promise. According to the proposal, a promise or a non-promise may be passed to await
, so await callback()
is universal.
There are only few edge cases when this may be needed. For instance, native async
functions use native promises internally and don't pick up global Promise
if its implementation was changed:
let NativePromise = Promise;
Promise = CustomPromiseImplementation;
Promise.resolve() instanceof Promise === true
(async () => {})() instanceof Promise === false;
(async () => {})() instanceof NativePromise === true;
This may affect function behaviour (this is a known problem for Angular and Zone.js promise implementation). Even then it's preferable to detect that function return value is not expected Promise
instance instead of detecting that a function is async
, because the same problem is applicable to any function that uses alternative promise implementation, not just async
(the solution to said Angular problem is to wrap async
return value with Promise.resolve
).
From the outside, async
function is just a function that unconditionally returns native promise, therefore it should be treated like one. Even if a function once was defined async
, it can be transpiled at some point and become regular function.
In ES6, a function that potentially returns a promise can be used with Promise.resolve
(lets synchronous errors) or wrapped Promise
constructor (handles synchronous errors):
Promise.resolve(fnThatPossiblyReturnsAPromise())
.then(result => ...);
new Promise(resolve => resolve(fnThatPossiblyReturnsAPromiseOrThrows()))
.then(result => ...);
In ES2017, this is done with await
(this is how the example from the question is supposed to be written):
let result = await fnThatPossiblyReturnsAPromiseOrThrows();
...
Checking if an object is a promise is a matter of a separate question, but generally it shouldn't be too strict or loose in order to cover corner cases. instanceof Promise
may not work if global Promise
was replaced, Promise !== (async () => {})().constructor
. This can happen when Angular and non-Angular applications interface.
A function that requires to be async
, i.e. to always return a promise should be called first, then returned value is checked to be a promise:
let promise = fnThatShouldReturnAPromise();
if (promise && typeof promise.then === 'function' && promise[Symbol.toStringTag] === 'Promise') {
// is compliant native promise implementation
} else {
throw new Error('async function expected');
}
TL;DR: async
functions shouldn't be distinguished from regular functions that return promises. There is no reliable way and no practical reason to detect non-native transpiled async
functions.
Short answer: Use instaceof
after exposing AsyncFunction
- see below.
Long answer: Don't do that - see below.
You can detect whether a function was declared with the async
keyword
When you create a function, it shows that it's a type Function:
> f1 = function () {};
[Function: f1]
You can test it with the instanceof
operator:
> f1 instanceof Function
true
When you create an async function, it shows that it's a type AsyncFunction:
> f2 = async function () {}
[AsyncFunction: f2]
so one might expect that it can be tested with instanceof
as well:
> f2 instanceof AsyncFunction
ReferenceError: AsyncFunction is not defined
Why is that? Because the AsyncFunction is not a global object. See the docs:
even though, as you can see, it's listed under Reference/Global_Objects
...
If you need easy access to the AsyncFunction
then you can use my unexposed
module:
to get either a local variable:
const { AsyncFunction } = require('unexposed');
or to add a global AsyncFunction
alongside other global objects:
require('unexposed').addGlobals();
and now the above works as expected:
> f2 = async function () {}
[AsyncFunction: f2]
> f2 instanceof AsyncFunction
true
The above code will test whether the function was created with the async
keyword but keep in mind that what is really important is not how a function was created but whether or not a function returns a promise.
Everywhere where you can use this "async" function:
const f1 = async () => {
// ...
};
you could also use this:
const f2 = () => new Promise((resolve, reject) => {
});
even though it was not created with the async
keyword and thus will not be matched with instanceof
or with any other method posted in other answers.
Specifically, consider this:
const f1 = async (x) => {
// ...
};
const f2 = () => f1(123);
The f2
is just f1
with hardcoded argument and it doesn't make much sense to add async
here, even though the result will be as much "async" as f1
in every respect.
So it is possible to check if a function was created with the async
keyword, but use it with caution because you when you check it then most likely you're doing something wrong.