Given that Promises are constructed by the syntax, you might think that p instanceof Promise
would be an acceptable check. But unfortunately, there are a number of reasons that’s not totally sufficient.
Mainly, you can receive a Promise value from another browser window (iframe, etc.), which would have its own Promise different from the one in the current window/frame, and that check would fail to identify the Promise instance.
Moreover, a library or framework may choose to vend its own Promises and not use the native ES6 Promise
implementation to do so. In fact, you may very well be using Promises with libraries in older browsers that have no Promise at all.
When we discuss Promise resolution processes later in this chapter, it will become more obvious why a non-genuine-but-Promise-like value would still be very important to be able to recognize and assimilate. But for now, just take my word for it that it’s a critical piece of the puzzle.
As such, it was decided that the way to recognize a Promise (or something that behaves like a Promise) would be to define something called a “thenable” as any object or function which has a then(..)
method on it. It is assumed that any such value is a Promise-conforming thenable.
Yuck! Setting aside the fact that this logic is a bit ugly to implement in various places, there’s something deeper and more troubling going on.
If you try to fulfill a Promise with any object/function value that happens to have a then(..)
function on it, but you weren’t intending it to be treated as a Promise/thenable, you’re out of luck, because it will automatically be recognized as thenable and treated with special rules (see later in the chapter).
This is even true if you didn’t realize the value has a then(..)
on it. For example:
doesn’t look like a Promise or thenable at all. It’s just a plain object with some properties on it. You’re probably just intending to send that value around like any other object.
But unknown to you, v
is also [[Prototype]]
-linked (see the this & Object Prototypes title of this book series) to another object o
, which happens to have a then(..)
on it. So the thenable duck typing checks will think and assume v
is a thenable. Uh oh.
Both and v2
will be assumed to be thenables. You can’t control or predict if any other code accidentally or maliciously adds then(..)
to Object.prototype
, Array.prototype
, or any of the other native prototypes. And if what’s specified is a function that doesn’t call either of its parameters as callbacks, then any Promise resolved with such a value will just silently hang forever! Crazy.
Sound implausible or unlikely? Perhaps.
But keep in mind that there were several well-known non-Promise libraries preexisting in the community prior to ES6 that happened to already have a method on them called then(..)
. Some of those libraries chose to rename their own methods to avoid collision (that sucks!). Others have simply been relegated to the unfortunate status of “incompatible with Promise-based coding” in reward for their inability to change to get out of the way.
The standards decision to hijack the previously nonreserved — and completely general-purpose sounding — property name means that no value (or any of its delegates), either past, present, or future, can have a then(..)
function present, either on purpose or by accident, or that value will be confused for a thenable in Promises systems, which will probably create bugs that are really hard to track down.
Warning: I do not like how we ended up with duck typing of thenables for Promise recognition. There were other options, such as “branding” or even “anti-branding”; what we got seems like a worst-case compromise. But it’s not all doom and gloom. Thenable duck typing can be helpful, as we’ll see later. Just beware that thenable duck typing can be hazardous if it incorrectly identifies something as a Promise that isn’t.