JavaScript
Thenables
A deep dive into Thenables in JavaScript, how they work, and how they compare to Promises.
What Are Thenables?
A Thenable is any object that has a .then() method. It behaves similarly to a Promise but doesn’t have to be a proper Promise object created with the Promise constructor.
Example of a Thenable:
const thenable = {
then: function (resolve, reject) {
setTimeout(() => resolve('Resolved from Thenable!'), 1000);
}
};
Promise.resolve(thenable).then(value => {
console.log(value); // Output: Resolved from Thenable!
});In this example:
thenableis a plain object with a.then()method.- Passing it to
Promise.resolve()causes it to behave like a Promise.
Why Do Thenables Matter?
JavaScript’s Promise mechanism is designed to interoperate with Thenables.
Promise.resolve()checks if the argument has a.then()method.- If yes, it treats it like a Promise and resolves accordingly.
This provides flexibility for custom async abstractions without necessarily using new Promise().
How Does Promise.resolve() Work?
const customThenable = {
then: function (resolve, reject) {
resolve('Resolved from custom Thenable!');
}
};
Promise.resolve(customThenable).then(console.log);
// Output: Resolved from custom Thenable!Key behavior:
- The
.then()method is called immediately. - The object is “assimilated” into the Promise chain.
Thenables vs Promises
| Feature | Thenables | Promises |
|---|---|---|
| Definition | Any object with a .then() method | Object created by new Promise() |
| Internal State | No standardized internal state | Internal states: pending, fulfilled, rejected |
| Standard Compliance | Not standardized by spec (just a convention) | Follows ECMAScript Promise spec |
| Automatic Chaining | Yes, via .then() | Yes, with .then(), .catch(), .finally() |
| Rejection Handling | Depends on implementation | Built-in rejection handling |
| Creation Syntax | Custom object { then(resolve, reject) { ... } } | new Promise((resolve, reject) => { ... }) |
| Guarantees | None inherently (depends on implementation) | Guaranteed resolution or rejection behavior |
When to Use Thenables
You might implement a Thenable when:
- Creating a lightweight custom async abstraction.
- Integrating a third-party async mechanism not based on Promises.
- Ensuring compatibility with Promise-based APIs without extra overhead.
Example use case:
class SimpleThenable {
constructor(value) {
this.value = value;
}
then(resolve, reject) {
resolve(this.value);
}
}
Promise.resolve(new SimpleThenable('Hello Thenable')).then(console.log);
// Output: Hello ThenableWhy Prefer Promises Over Thenables
- Promises provide consistent, well-defined behavior.
- Thenables may have unpredictable implementations.
- Promises support
.catch(),.finally(), and chaining reliably.
Unless there is a compelling reason, prefer new Promise() or async/await over custom Thenables.
Example Comparison
Using a Promise:
const promise = new Promise((resolve) => {
setTimeout(() => resolve('Promise resolved'), 1000);
});
promise.then(console.log);
// Output after 1 second: Promise resolvedUsing a Thenable:
const thenable = {
then: function (resolve, reject) {
setTimeout(() => resolve('Thenable resolved'), 1000);
}
};
Promise.resolve(thenable).then(console.log);
// Output after 1 second: Thenable resolved