Skip to main content

How to Check if a Function is Async or Returns a Promise in JavaScript

When working with asynchronous operations, you often need to know if a function is async or if it returns a Promise. These are related but distinct questions. An async function always returns a Promise, but a regular function can also return a Promise.

This guide will teach you the modern, standard methods for answering both of these questions. You will learn a reliable way to check if a function was declared with the async keyword and a safe way to determine if a function's return value is "thenable" (i.e., a Promise).

Question 1: Is This Function Declared as async?

If you need to know if a function was specifically written using the async keyword, you can inspect the name of its constructor.

Every async function in JavaScript is an instance of the AsyncFunction constructor, while every regular function is an instance of the Function constructor. We can check the constructor.name property to tell them apart.

For example, we want to distinguish between a function declared with async and a regular function.

// Problem: How to tell these two apart without calling them?
async function myAsyncFunction() {}
function myRegularFunction() {}

The recommended solution:

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

// Example Usage:
const myAsyncFn = async () => {};
const myRegularFn = () => {};

console.log(isAsync(myAsyncFn)); // Output: true
console.log(isAsync(myRegularFn)); // Output: false
note

This is a reliable and synchronous way to check how a function was declared.

Question 2: Does This Function Return a Promise?

This is a more complex question. An async function will always return a Promise, but a regular function can also be written to return a Promise.

// This is a regular function that returns a Promise.
function regularFnReturnsPromise() {
return Promise.resolve(42);
}

console.log(isAsync(regularFnReturnsPromise)); // Output: false
note

The only way to know for sure if a regular function returns a Promise is to call it and inspect the result.

warning

Warning: Calling the function may have side effects (e.g., making an API call), so this should be done with caution.

This function first checks if the function is async, and if not, it calls the function to see if the return value is "thenable" (has a .then method, which is the defining characteristic of a Promise).

function isThenable(value) {
return value && typeof value.then === 'function';
}

function returnsPromise(func) {
// Easiest check: is the function declared as async?
if (func.constructor.name === 'AsyncFunction') {
return true;
}

// Harder check: call the function and see what it returns.
// This is risky if the function has side effects!
try {
const returnValue = func();
return isThenable(returnValue);
} catch (error) {
// If the function throws an error, it doesn't return a promise.
return false;
}
}

// Example Usage:
console.log(returnsPromise(async () => {})); // Output: true
console.log(returnsPromise(() => Promise.resolve())); // Output: true
console.log(returnsPromise(() => 42)); // Output: false

The Safer Alternative: Always Treat the Return Value as a Promise

Because calling a function to check its return type can be risky, a much safer and more common pattern is to simply wrap the function's return value in Promise.resolve().

note

The Promise.resolve(value) method is idempotent:

  • If you pass it a Promise, it returns that same Promise.
  • If you pass it a non-Promise value (a "thenable" or a plain value like a number), it returns a new Promise that immediately resolves with that value.

This means you can guarantee that you have a Promise to work with, regardless of what the original function returned.

Solution:

function aRegularFunction() {
return 'Hello, World!';
}

async function anAsyncFunction() {
return 'Hello from async!';
}

// Wrap the return value and then chain `.then()`
Promise.resolve(aRegularFunction()).then(result => {
console.log(result); // Output: "Hello, World!"
});

Promise.resolve(anAsyncFunction()).then(result => {
console.log(result); // Output: "Hello from async!"
});
note

This is the best practice for handling functions where you are unsure of the return type. It allows you to write consistent, asynchronous code without risky introspection.

Conclusion

Checking the nature of a function requires you to be precise about what you're asking.

  • To check if a function was declared with the async keyword, use the func.constructor.name === 'AsyncFunction' check.
  • To check if a function returns a Promise, you must either confirm it's async or risk calling it to inspect the result.
  • The safest and most robust pattern is to avoid checking altogether and simply wrap the function's return value in Promise.resolve(). This guarantees you can treat the result as a Promise.