When Promises Try to Understand You: Rethinking Thenable Assimilation in JavaScript
Thenable assimilation is the process where a Promise detects objects with a .then method and treats them like Promises, recursively unwrapping their resolution so that promise-like values can interoperate seamlessly.
At first, a Promise feels simple.
You resolve it with a value.
And that value flows through.
Promise.resolve(10).then(console.log)Thereâs no confusion.
You give it 10, and it gives you back 10.
But then something unexpected happens.
When a Value Isnât Just a Value
Consider this:
Promise.resolve({
then(resolve) {
resolve(42)
}
}).then(console.log)You didnât pass a Promise.
You passed an object.
And yet, the result behaves as if you did.
It resolves to 42.
Not the object itself.
But what the object decided to become.
The Hidden Behavior
This is where something subtle reveals itself:
A Promise does not simply accept a valueâit tries to interpret it
If the value has a .then method, JavaScript pauses.
It doesnât treat it as plain data.
It treats it as something promise-like.
And instead of storing it, it calls it.
Thenables: Promises in Disguise
An object with a .then method is called a thenable.
Itâs not a real Promise.
But it behaves like one.
And JavaScript leans into that behavior.
It assumes:
âIf you have a then, you must know how to resolve yourself.â
So it lets the object take control.
Assimilation, Not Assignment
This process is called thenable assimilation.
And the word matters.
Because the Promise doesnât just receive the valueâ
it adopts it.
It follows the .then method.
It waits for it to resolve.
And it becomes whatever that resolution produces.
A Recursive Unfolding
What makes this even more interesting is that the process can repeat.
A thenable can resolve to another thenable.
Promise.resolve({
then(resolve) {
resolve({
then(resolve) {
resolve("deep")
}
})
}
}).then(console.log)The Promise keeps unwrapping.
Layer by layer.
Until it reaches something that is no longer a thenable.
Only then does it stop.
Why This Exists
At first, this behavior might feel unnecessary.
Why not just treat everything as a value?
But JavaScript was designed to work across boundaries.
Different libraries.
Different implementations.
Not all promises are created equally.
Thenable assimilation allows them to cooperate.
To speak a common language.
Even if they come from different sources.
The Trade-Off
This flexibility comes with risk.
If a .then method behaves incorrectlyâ
throws errors, or resolves unpredictablyâ
the Promise inherits that behavior.
It trusts the object.
And sometimes, that trust can be misplaced.
A Different Way to See Promises
Most of the time, you think of Promises as containers of values.
But this reveals something deeper.
They are not just containers.
They are interpreters.
They look at what you give themâ
and decide how to handle it.
A Subtle Shift
Instead of thinking:
âPromise.resolve(value) stores the valueâ
You begin to think:
âPromise.resolve(value) tries to understand what this value isâ
And if that value behaves like a Promiseâ
it becomes one.
A Final Thought
Thenable assimilation is one of those behaviors that stays hidden until it surprises you.
But once you see it, your understanding of Promises changes.
Because what you pass into a Promise is not always what comes out.
Sometimes, JavaScript looks deeper.
And instead of taking your value as it isâ
it asks:
âWhat are you trying to become?â