In JavaScript, what happens when you `await` something that is not a promise — for example `await 42`, or `await` on a plain object that merely has a `then` method?
answer
- it resolves, it does not demand a promise
- the check is structural, not nominal
- one property name decides everything
- a pause happens either way
- a silent hang is the worst case
basics
~20 sawait resolves whatever it is given. A non-thenable such as 42 simply becomes the result, and any object with a callable then method is treated as a promise: its then is called and await produces whatever that method resolves with. Either way the function suspends first.
solid answer
~40 s`await` does not require a promise — it runs promise resolution on its operand. If the value is not a **thenable** (an object with a callable `then` method), `await` yields the value itself: `await 42` is `42`. If it *is* a thenable, `await` calls its `then` method, passing resolve and reject functions, and produces whatever that call settles with — this duck typing is why any library's promise-like object is awaitable. The catch is that this is structural, not nominal: an ordinary data object that happens to carry a `then` property gets hijacked, so returning `{ then: 'yes' , ...}`-shaped data from an async function can silently change or hang the result. And `await` on a non-promise is not a no-op — the function still suspends and resumes later, so it still reorders code.
code
javascript · 12 linesconst thenable = {
then(resolve) { setTimeout(() => resolve('settled by then()'), 10); }
};
(async () => {
console.log(await 42); // 42 (not a thenable: passed through)
console.log(await thenable); // settled by then()
// structural detection: ordinary data with a `then` gets hijacked
const record = { id: 7, then(resolve) { resolve('hijacked'); } };
console.log(await record); // hijacked — the object never arrives
})();go deeper
Know that await accepts any value: a non-promise comes straight back, so awaiting something that turns out not to be a promise is harmless rather than an error.
Explain the thenable rule precisely — a callable then property makes any object awaitable — and note that suspension happens regardless, so awaiting a plain value still reorders code.
Show the failure mode you would actually debug: data carrying a then property that gets resolved instead of delivered, or a thenable that never settles and hangs a request with no error to trace.
Treat it as a boundary-design rule: values crossing async boundaries should be wrapped or validated rather than returned bare, so that no property name in foreign data can change your control flow.
## await resolves, it does not require a promise The operand of `await` is passed through the same resolution machinery a promise uses when it is resolved with a value. Two branches exist: - **Not a thenable** — any primitive, or an object with no callable `then`. The result of the `await` expression is that value, unchanged. - **A thenable** — an object or function with a callable `then` property. Its `then` is invoked with a resolve and a reject function, and the `await` produces whatever that invocation settles with. ```js console.log(typeof (async () => await 42)); // await accepts anything const thenable = { then(resolve) { resolve('from a thenable'); } }; (async () => { console.log(await 42); // 42 console.log(await 'text'); // text console.log(await null); // null console.log(await thenable); // from a thenable })(); ``` ## Suspension still happens The most-missed part: `await 42` is not free and not a no-op. The function suspends at that point and its continuation is queued, exactly as for a real promise. Adding an await for a plain value therefore changes the relative order of your code, which occasionally shows up as an accidental fix or an accidental bug. ## Thenable duck typing is a feature, and a trap Because the check is "does it have a callable `then`", any promise-like object from any library interoperates with `await` without conversion. That interop was a deliberate design choice. The same rule bites when a plain data object accidentally has a `then` method. Since promise *resolution* also applies to values returned from an async function, the object never reaches the caller intact: ```js async function getRecord() { return { id: 1, then(resolve) { resolve('hijacked'); } }; } getRecord().then(v => console.log(v)); // hijacked — the object is gone ``` Worse, a `then` that never calls either callback makes the await hang forever with no error: ```js const blackHole = { then() { /* never settles */ } }; // await blackHole → the function is suspended for the process's lifetime ``` This is a real hazard when awaiting data you did not author — a parsed JSON payload, a user-supplied config object, a module namespace object, or a class instance with a method that happens to be named `then`. The defensive habits are: never name a domain method `then`, and if you must pass through arbitrary data, wrap it (`return { value: data }`) instead of returning it bare from an async function. ## `then` is only honoured once The resolve and reject functions handed to a thenable's `then` are single-shot in effect: the first settlement wins and later calls are ignored. A badly written thenable that resolves twice, or resolves and then throws, cannot corrupt the awaiting function's state — it just gets the first outcome. If the `then` method itself throws before settling, that becomes a rejection, so the await throws. ## Getter hazards The `then` property is read from the value during resolution. If `then` is an accessor with a side effect, that side effect happens simply because the value was awaited or returned from an async function. It is a small but genuine reason to avoid clever getters on objects that cross async boundaries. ## What to say in an interview State the rule in one sentence — `await` resolves its operand, unwrapping anything thenable and passing anything else straight through — then add the two consequences that show depth: the suspension is unconditional, and thenable detection is structural, so an accidental `then` property changes your data's fate.
- Why does `await` use duck typing on `then` instead of checking `instanceof Promise`?For interoperability. Promise-like objects from other libraries, other realms such as an iframe, or older promise implementations all work with `await` and with promise resolution without conversion. An `instanceof` check would fail across realms and lock out every non-native implementation, which is exactly what the thenable rule was designed to avoid.
- What happens if a thenable's `then` method throws before calling either callback?The throw becomes a rejection, so the `await` throws at that point in the async function. If the thenable had already settled before throwing, the first settlement stands and the later throw is ignored — settlement is single-shot, so a misbehaving thenable cannot change an outcome it has already produced.
- How can an accidental `then` property realistically get into your data?Parsed JSON from an untrusted or unusual source, a class whose domain method is genuinely called `then`, a config object built by users, or an object spread that pulled in a `then` from elsewhere. Any of those returned bare from an async function, or awaited, is resolved rather than delivered. Wrapping the value avoids it entirely.
saying these in an interview costs you the question
- Says awaiting a non-promise is an error
- Claims await on a plain value skips suspension
- Thinks only real Promise instances are awaitable
- Believes an object with then is still delivered intact
- Says a never-settling thenable throws a timeout error