All Articles

7 Ways to Detect JavaScript Async Function

monika-pejkovska-JI11YohHcGM-unsplash

Photo by Monika Pejkovska on Unsplash

Detect Native Async Functions

There 5 ways to detect an async function if your JavaScript runtime environment support async function natively (e.g. node >= 7.6.0).

1. By the name of the constructor

You can identify native async functions by the name of its constructor as stated in the tc39 spec:

The value of the name property of the AsyncFunction is “AsyncFunction”.

// For example we have the following async function
async function asyncFn() {}

asyncFn.constructor.name; // 'AsyncFunction'

2. By converting the function to string

Again according to the tc39 spec:

The initial value of the @@toStringTag property is the string value “AsyncFunction”.

asyncFn[Symbol.toStringTag]; // 'AsyncFunction'

or the old school way:

Object.prototype.toString.call(asyncFn);
// '[object AsyncFunction]'

3. By converting the constructor of the function to string

Similar to the second one above. Instead of converting the function itself we stringify its constructor:

String(async.constructor);
// 'function AsyncFunction() { [native code] }'

4. Using instanceof

AsyncFunction is not global variable despite it’s defined as an intrinsic object in the tc39 spec. However, we can access the base class through constructor of an async function:

const AsyncFunction = (async function(){}).constructor;
asyncFn instanceof AsyncFunction; // true

5. Using util.types.isAsyncFunction for [email protected]

If you are using NodeJS >= 10.0 then there is a helper function which can do the detection for you:

const util = require('util');
util.types.isAsyncFunction(async function () {}); // true

Detect Transpiled Async Functions

Because tools like Babel transpiles async functions to generator functions and then transpiles generator to regular functions (see more about this in the proposal). Technically speaking, there’s no way to detect an async function without calling it since all async functions will be transpiled down to regular ES5 functions by the transpiler. Moreover, according to the proposal, after transpiled, the async function has no real difference between a regular function that returns a Promise. Then the only thing we can do in a transpiled environment is to check the returned value of the questioning function:

6. By checking instanceof Promise

asyncFn() instanceof Promise; // true

The above is not very reliable as it looks. The function in question might use some 3rd-party Promise implementation instead of the one provided by the transpiler. Then the most reliable way would be the next one.

7. By checking .then

const value = asyncFn();
value && typeof value.then === 'function'; // true

Summary

If you are working on some code that needs to deal with functions which could either be async or sync, the best option is to wrap the original functions and return Promises no matter what. Then you could have a unified return value and make the interface of that piece of code consistent. Below, is just some idea to get started. You may need to tweak it based on your actual requirement.

function asyncify(fn) {
  let isAsync = false;
  return function() {
    const value = fn.apply(null, arguments);

    if (isAsync) {
      return value;
    }

    if (value && typeof value.then === 'function') {
      isAsync = true;
      return value;
    }

    return Promise.resolve(value);
  };
}

Thanks for reading!