Why instanceof Promise Is Not a Reliable Check in JavaScript

Why instanceof Promise Is Not a Reliable Check in JavaScript
Photo by Stephen Harlan / Unsplash

When working with asynchronous programming in JavaScript, it is tempting to check whether a value is a Promise using instanceof Promise. At first glance, this seems logical—after all, promises are the foundation of async/await, so why not verify them directly?

But here’s the catch: instanceof Promise is not a good or reliable check. Let’s dive into why, and what better practices you should adopt.


The Problem with instanceof Promise

1. Cross-Realm Issues

JavaScript runs in different "realms"—for example, inside an iframe or a worker thread, the global Promise constructor is technically a different object.

This means:

iframePromise instanceof Promise // false

Even though it’s a perfectly valid promise, the check fails because it comes from a different global environment.


2. Custom Thenables

The JavaScript spec defines promises in terms of thenables—objects with a .then method.

That means any object that implements .then can be awaited:

const thenable = {
  then(resolve) {
    resolve("Hello from a thenable!");
  }
};

await thenable; // Works fine!

Here, thenable instanceof Promise is false, but await doesn’t care—it just looks for a .then function.

This is why many libraries (e.g., Bluebird, ORM query builders, or custom async utilities) return thenables without necessarily being actual Promise objects.


3. Spec Compliance

The ECMAScript specification explicitly states that both await and Promise.resolve will handle any thenable, not just native promises.

So by design, JavaScript treats awaitable values more broadly than just Promise instances.


A Better Way: Duck Typing

Instead of relying on instanceof, the recommended approach is duck typing—checking whether the value behaves like a promise:

function isThenable(value) {
  return value != null && typeof value.then === "function";
}

This works for:

  • Native promises
  • Cross-realm promises
  • Custom thenables

Essentially, if it walks like a duck (has a .then method), it’s treated like a duck.


Other Considerations

1. Async Functions Always Return Promises

Even if you return a primitive value inside an async function:

async function foo() {
  return 42;
}

Calling foo() will always return a promise. No need to check with instanceof.


2. Avoid Over-Checking

In many cases, you don’t need to check at all. If you’re writing async code, you can often just await the value and trust the runtime:

const result = await maybePromise; 

If it’s not awaitable, you just get the value back immediately.


3. Interoperability Across Libraries

When integrating with third-party libraries, don’t enforce Promise checks unless absolutely necessary. Some libraries intentionally return custom thenables for advanced scheduling, cancellation, or debugging features. Rejecting them because they fail an instanceof test could break compatibility.


4. Error Handling

One subtle point: thenables might misbehave (e.g., badly written .then implementations). This is rare, but if you’re building low-level libraries, you should be aware that not all thenables are well-behaved. In such cases, defensive coding or normalization (Promise.resolve(thenable)) is a good idea.


The Takeaway

  • Don’t use instanceof Promise as a general-purpose check.
  • Do use duck typing (typeof value?.then === "function") if you really need to distinguish thenables.
  • Often, you can skip the check entirely and just await—JavaScript will do the right thing.
  • Remember: async/await is about behavior, not about constructors.

In summary: The power of JavaScript’s async model comes from its flexibility with thenables, not just promises. By avoiding strict instanceof checks, you make your code more robust, interoperable, and future-proof.

Support Us