Why instanceof Promise Is Not a Reliable Check in JavaScript
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.
Comments ()