Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know if a function is async?

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??

like image 933
Facundo Matteo Avatar asked Jul 21 '16 15:07

Facundo Matteo


People also ask

Which functions are asynchronous?

An asynchronous function is any function that delivers its result asynchronously – for example, a callback-based function or a Promise-based function. An async function is defined via special syntax, involving the keywords async and await . It is also called async/await due to these two keywords.

What does it mean for a function to be async?

An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

Are all JavaScript functions asynchronous?

JavaScript functions are not asynchronous. Some very limited set of functions have an asynchronous API: addEventListener , setTimeout , setInterval .


2 Answers

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.

like image 193
Estus Flask Avatar answered Oct 18 '22 11:10

Estus Flask


As long as only the native async functions are used (which is usually the case), I prefer this simple way:

theFunc.constructor.name == 'AsyncFunction' 
like image 32
Alexander Avatar answered Oct 18 '22 12:10

Alexander