88

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() {
 // Some async actions
}

So I want to execute await callback() or callback() depending on the type of function that it is receiving.

Is there a way to know the type of the function??

Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
Facundo Matteo
  • 1,687
  • 1
  • 12
  • 19
  • 7
    Don't try to detect it and do different things depending on what you get. Clearly document whether you support callbacks that return promises or not, then treat them as such. (Hint: if you `await` a non-promise, it automatically wraps it anyway) – Bergi Jul 15 '18 at 15:15
  • 3
    the whole point of async is to not have callbacks, right? – Felipe Valdes Oct 11 '19 at 16:53

8 Answers8

75

Theory

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).

Practice

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.

A function that can return a promise

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();
...

A function that should return a promise

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.

Estus Flask
  • 150,909
  • 47
  • 291
  • 441
  • This is not working on my end. `AsyncFunction !== Function` is always false even though I have functions with keyword `async` passed as an argument to an `it()` spec. I am using Typescript by the way. Could you please take a look at this [question](https://stackoverflow.com/questions/49060405/how-to-programmatically-find-if-a-function-is-async) and provide your insights. I have been trying so many different ways but didn't succeed yet. :( – Tums Mar 05 '18 at 18:44
  • @Tums That's because `AsyncFunction !== Function` check is there to avoid *false positives*. There won't be *true positives* in transpiled code because `async` functions don't differ from regular ones in transpiled code. – Estus Flask Mar 05 '18 at 18:58
  • I'm writing a hook function, the function takes an object, target and hook... how do i know if i have to await? – Erik Aronesty Jun 11 '20 at 19:47
  • @ErikAronesty Can you provide a one-liner example? If a value can be a promise or not a promise, you need to `await`, it works for promises and non-promises. This is what the last snippet in the answer shows. – Estus Flask Jun 11 '20 at 19:56
  • @EstusFlask: https://stackoverflow.com/questions/10273309/need-to-hook-into-a-javascript-function-call-any-way-to-do-this/62333813#62333813 See how I can't just 'await'... because then I'd be changing the semantics of the hooked function. – Erik Aronesty Jun 11 '20 at 21:27
  • @ErikAronesty Yes, that's how it's usually done. Instead of checking if a function is `async`, you can check that its return value is native promise (`result instanceof Promise`) or thenable (``result && typeof result.then === 'function'``). – Estus Flask Jun 12 '20 at 05:44
30

I prefer this simple way:

theFunc.constructor.name == 'AsyncFunction'
Alexander
  • 6,090
  • 2
  • 44
  • 58
  • 1
    This also have the advantage to be more performant than a stringify :) – GLAND_PROPRE Feb 21 '20 at 15:42
  • 1
    The problem with duck typing is that custom function passes this check, `theFunc = new class AsyncFunction extends Function {}`. But transpiled `async` function doesn't, `theFunc = () => __awaiter(void 0, void 0, void 0, function* () { })`. – Estus Flask Apr 06 '20 at 21:43
  • 2
    Of course @EstusFlask, you are totally right. If it's your case - you need a more complex solution. But in a "real world" (not super special or artificial cases) - one could use this solution, instead of overkill monster checkers. But one should be aware of what you are saying, thank you for your comment! – Alexander Apr 07 '20 at 23:02
  • Why not use `=== 'AsyncFunction'` like what @theVoogie suggested? – themefield Oct 26 '20 at 00:24
  • @Alexander, in a real world non-async functions return promises all the time, just like async functions. – cababunga Nov 06 '20 at 19:18
26

Both @rnd, and @estus are correct.

But to answer the question with an actual working solution here you go

function isAsync (func) {
    const string = func.toString().trim();

    return !!(
        // native
        string.match(/^async /) ||
        // babel (this may change, but hey...)
        string.match(/return _ref[^\.]*\.apply/)
        // insert your other dirty transpiler check

        // there are other more complex situations that maybe require you to check the return line for a *promise*
    );
}

This is a very valid question, and I'm upset that someone down voted him. The main usecase for this type of checking is for a library/framework/decorators.

These are early days, and we shouldn't downvote VALID questions.

Chad Scira
  • 9,075
  • 3
  • 49
  • 48
  • I guess the problem with this question is that it is XY problem. As it was already mentioned, async functions just return promises, so they shouldn't be detected at all. Btw, they cannot be reliably detected in minified transpiled code, `_ref` won't be there. – Estus Flask Nov 02 '17 at 18:27
  • 1
    A slight issue beyond this, is a lot of times people will wrap node-style callbacks into promise wrappers to use with async functions, so the function may be async to a reasonable degree, but not really async. await may work in either case... where it could get complicated is async generators. – Tracker1 Jan 22 '19 at 21:16
  • 1
    This is still a valid question, though. In code that only uses async and await, it matters to know whether a function was declared async or not, and it is irrelevant how async/await was imlemented under the hood. For example, if your API wrapper needs to make sure that a handler was declared async, so that it can throw an error the user can fix, then you want an answer to the original question, and this one will do just fine. So to add to this answer: another way to check natively is `fn.constructor.name`, which will be `AsyncFunction` for async functions. – Mike 'Pomax' Kamermans Jun 15 '19 at 21:03
  • @Mike'Pomax'Kamermans The question resulted from incorrect understanding of `await` semantics. It doesn't matter if a function is `async` in any practical scenario I'm aware of. `async` is just a function that unconditionally returns native promise - and should be treated like one. `async` can become transpiled at some point, this shouldn't ruin an app. For the scenario you describe it's correct for a wrapper to call a function and assert a value to be promise, not assert a function to be `async`. If it needs to prevent invalid handlers ASAP, this has to be enforced at design time with TS/Flow – Estus Flask Sep 11 '20 at 21:42
  • Remember that just because you aren't aware of any practical scenario, that doesn't mean [there are none](https://github.com/pomax/socketless). So that's something new to learn: you can discover whether a function is async or not, which means you can write code that will "do things" with or to async functions while leaving regular functions alone (or vice versa). Is it useful for normal code? No, I also can't think of a scenario in which you'd need that. But is that important for code analysis, AST builders, or transpilers that are themselves written in JS? Yeah: pretty important, actually. – Mike 'Pomax' Kamermans Sep 11 '20 at 22:14
  • @Mike'Pomax'Kamermans Thanks for sharing. You use `async` check there, don't you? You have to be aware that client lib will make Angular users suffer because the framework requires `async` to be transpiled in order for change detection to work (and server lib will affect Electron+Angular apps). It's possible to use `async` for semantic purposes but I'd consider it a misuse, this is what decorators are for. Extra data in method names like you did with `:` is also common. It's surely needed for code analysis but that's a reasonable exception, not something that regular users look here for. – Estus Flask Sep 11 '20 at 23:12
  • If Angular isn't compatible with `async`, then that's a good reason to not use Angular, frankly. Plenty of other frameworks, and modern vanilla JS, to pick from. As for what someone might use this for: I'm hardly a regular user, and yet I commented here because that's that SO post that Google found me when I was looking for a decent way to check for async-ness. Good stackoverflow answers help everyone, not just regular users ;) – Mike 'Pomax' Kamermans Sep 12 '20 at 01:02
12

In case you're using NodeJS 10.x or later

Use the native util function.

   util.types.isAsyncFunction(function foo() {});  // Returns false
   util.types.isAsyncFunction(async function foo() {});  // Returns true

Do keep all the concerns in mind from above ansers though. A function that just returns by accident a promise, will return a false negative.

And on top of that (from the docs):

Note that this only reports back what the JavaScript engine is seeing; in particular, the return value may not match the original source code if a transpilation tool was used.

But if you use async in NodeJS 10 and no transiplation. This is a nice solution.

Segers-Ian
  • 907
  • 7
  • 18
4

TL;DR

Short answer: Use instaceof after exposing AsyncFunction - see below.

Long answer: Don't do that - see below.

How to do it

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

Why you shouldn't do it

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.

Summary

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.

rsp
  • 91,898
  • 19
  • 176
  • 156
  • What I can understand with "Why you shouldn't do it", it's fine to check if a function is declared with `async` to know if it is doing some async/await operation inside but returning nothing. – Amit Kumar Gupta Mar 29 '18 at 07:05
  • 1
    @AmitGupta It doesn't return nothing. It returns a promise. – Estus Flask Apr 11 '18 at 11:44
  • If you have a codebase that mixes async/await (which requires knowing nothing about promises) and promise functions, really _that's_ the thing you shouldn't be doing. The nice thing about async/await is that the implementation details become irrelevant: you don't `then().catch()` an async function, you `try/await` it instead. So yeah, you totally _should_ check the function's type if you leitimately need to know whether it's async or not, but not by using `instanceof`: use `fn.constructor.name` instead. If it's `AsyncFunction` instead of `Function`, you know it's an async function. – Mike 'Pomax' Kamermans Jun 15 '19 at 21:11
4

It seems that await can be used for normal functions too. I'm not sure if it can be considered "good practice" but here it is:

async function asyncFn() {
  // await for some async stuff
  return 'hello from asyncFn' 
}

function syncFn() {
  return 'hello from syncFn'
}

async function run() {
  console.log(await asyncFn()) // 'hello from asyncFn'
  console.log(await syncFn()) // 'hello from syncFn'
}

run()
gyo
  • 1,285
  • 15
  • 26
3

Hei,

Here is an approach provided by David Walsh in his blogpost:

const isAsync = myFunction.constructor.name === "AsyncFunction";

Cheers!

theVoogie
  • 1,320
  • 1
  • 16
  • 22
0

You can assume at begin that callback is promise:

export async function runSyncOrAsync(callback: Function) {

  let promisOrValue = callback()
  if (promisOrValue instanceof Promise) {
    promisOrValue = Promise.resolve(promisOrValue)
  }
  return promisOrValue;
}

and them in your code you can do this:

await runSyncOrAsync(callback)

which will solve your problem with unknowing callback type....

Dariusz Filipiak
  • 2,343
  • 4
  • 22
  • 35