How would you implement a minimal promise from scratch?
A promise is a state machine with three states, a value, and a queue of continuations. That is genuinely all a promise is, and the parts you leave out are about interoperability rather than about the core idea.
The states are pending, fulfilled and rejected. A promise starts pending and settles exactly once, which means the transition happens a single time and every later attempt is ignored.
The queue holds the handlers that were registered before the promise settled. When the promise settles, the queue is flushed and each handler runs. If the promise was already settled when a handler was registered, the handler runs as a microtask instead, which keeps the timing consistent either way.
Two details make the real thing hard. The first is that then has to return a new promise, so that the result of a handler can be resolved or rejected in turn, which is what makes chaining work. The second is that the resolution procedure is not just a resolve call. If you resolve with a thenable, the promise adopts its state, which is why returning a promise from an async function works.
What you can skip in an interview version. The thenable adoption, the species mechanism for subclassing, and the careful ordering between queued and immediate handlers. What you must not skip is the single settlement, the queue, and the fact that handlers run asynchronously rather than during the call to then.