Thenable Object

ID

javascript.thenable_object

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

async, naming, reliability

Description

Reports an object literal or a class that declares a callable then member, which silently turns the value into a promise as far as the language is concerned.

Only a then whose value is a function is reported: the specification calls the member only when it is callable, so {then: 'tomorrow'} is an ordinary property and not a thenable. A static then() is not reported either — the machinery reads the member off the value it is adopting, and that is an instance.

Rationale

// Bad
const step = {
  name: 'deploy',
  then(next) {
    return this.chain(next);
  }
};

const done = await run();   // `done` is whatever `then` passed on, never `step`
  • The promise machinery does not check types, it checks for a member. await, Promise.resolve and every .then in a chain look for a property named then and, if it is callable, hand it a resolve and a reject function and adopt whatever it produces.

  • An object that declares then for its own reasons — a builder, a pipeline step, a query object, a state machine — is therefore assimilated the moment it reaches any of them. Returning it from an async function is enough, since the return value is resolved.

  • Nothing reports an error. await obj yields the value obj.then passed to its callback instead of obj, and if then never calls back the await hangs for ever with no stack to look at.

Remediation

Rename the member. then belongs to the promise protocol; any other verb keeps the object an ordinary value.

// Good
const step = {
  name: 'deploy',
  andThen(next) {
    return this.chain(next);
  }
};

// Good — if the object really is a promise, say so by returning one
class Job {
  run() {
    return this.queue.enqueue(this);   // a real Promise, not a look-alike
  }
}

© 2026 Xygeni. All rights reserved