A thenable is any object with a callable then() method. Promises are thenable, but not every thenable is a real Promise. JavaScript treats thenables as promise-like values and can adopt them with Promise.resolve() or await.
This matters when you work with libraries, custom async wrappers, or objects that behave like promises without being created by the Promise constructor. For related promise behavior, see JavaScript Promise.resolve() and JavaScript Promise.reject().
Tested on: Node.js v20.18.2. A short note after each runnable snippet describes what you should see in the console.
Quick reference
| Situation | What to do |
|---|---|
| Unknown value might be Promise or thenable | Promise.resolve(x) then chain .then / await |
| Implement minimal interop | Provide then(onFulfilled, onRejected) like Promises do |
| Prefer real Promises in your public API | Wrap with Promise.resolve before returning |
Method 1: A minimal thenable
A thenable only needs a then method that eventually calls resolve or reject.
const thenable = {
then(resolve) {
resolve("thenable resolved");
},
};
console.log("has-then:", typeof thenable.then);You should see one line logging has-then: function.
Promise.resolve() and await can adopt this object even though it is not a Promise instance.
Method 2: Resolve a thenable with Promise.resolve
Promise.resolve flattens thenables into the same microtask flow as native promises.
const thenable = {
then(resolve) {
resolve("thenable resolved");
},
};
Promise.resolve(thenable).then((value) => {
console.log("thenable:", value);
});You should see one line logging thenable: thenable resolved.
Method 3: await adopts a thenable
Inside an async function, await uses the same adoption rules as Promise.resolve.
const thenable = {
then(resolve) {
resolve("thenable resolved");
},
};
(async () => {
const value = await thenable;
console.log("awaited:", value);
})();You should see one line logging awaited: thenable resolved.
Method 4: Thenable vs real Promise
Both can carry a fulfilled value, but only a real Promise is an instance of Promise and passes Promise-specific checks you might use in tests.
const promise = Promise.resolve("ready");
const customThenable = {
then(resolve) {
resolve("ready");
},
};
console.log("promise-instance:", promise instanceof Promise);
console.log("thenable-instance:", customThenable instanceof Promise);
Promise.all([promise, Promise.resolve(customThenable)]).then((vals) => {
console.log("merged:", vals.join("|"));
});You should see three lines, in order: promise-instance: true, thenable-instance: false, merged: ready|ready.
When you normalize unknown inputs, Promise.resolve(x) is a practical bridge because it accepts both promises and thenables.
Common questions about thenables
What is a thenable in JavaScript?
A thenable is an object that has a then() method and can be treated like a promise-like value.
Is every promise a thenable?
Yes. A Promise has a then() method, so it is thenable. The reverse is not true.
What is the difference between thenable and promise?
A Promise is a native async object with extra methods (catch, finally, …). A thenable is any object that implements then() and can be adopted by Promise APIs.
When should I use Promise.resolve with a thenable?
Use Promise.resolve() when a helper returns a promise-like object and you want a real Promise for chaining or async/await.
Summary
A JavaScript thenable is a promise-like object with a then() method. Use Promise.resolve() or await to adopt thenables into normal promise flow. This pattern matters when you work with custom async wrappers or libraries that return promise-compatible objects instead of native promises—especially next to Promise.resolve() and Promise.reject().
