HVRDHVRD
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:

  • thenable is 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

FeatureThenablesPromises
DefinitionAny object with a .then() methodObject created by new Promise()
Internal StateNo standardized internal stateInternal states: pending, fulfilled, rejected
Standard ComplianceNot standardized by spec (just a convention)Follows ECMAScript Promise spec
Automatic ChainingYes, via .then()Yes, with .then(), .catch(), .finally()
Rejection HandlingDepends on implementationBuilt-in rejection handling
Creation SyntaxCustom object { then(resolve, reject) { ... } }new Promise((resolve, reject) => { ... })
GuaranteesNone 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 Thenable

Why 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 resolved

Using 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