# Promises — Part 1: The Fundamentals

## 1. What a Promise Is

A `Promise` is an object representing the eventual result of an asynchronous operation. It exists in exactly one of three states:

| State | Meaning | Can transition to |
|---|---|---|
| **pending** | operation not finished yet | fulfilled or rejected |
| **fulfilled** | completed successfully, has a value | (final — stays fulfilled forever) |
| **rejected** | failed, has a reason (usually an Error) | (final — stays rejected forever) |

Once a Promise settles (fulfilled or rejected), it **never changes state again** — this is what makes Promises reliable to reason about, unlike raw callbacks which could theoretically be called multiple times.

![Pending transitions to fulfilled via resolve or to rejected via reject, then never changes again](diagrams/01-promise-states.svg)

## 2. Creating a Promise

```js
const promise = new Promise((resolve, reject) => {
  const success = Math.random() > 0.3;
  setTimeout(() => {
    if (success) resolve("data loaded");
    else reject(new Error("failed to load"));
  }, 100);
});
```

The executor function (passed to `new Promise`) runs **immediately and synchronously**, but `resolve`/`reject` are typically called later, once the async work completes.

## 3. Consuming a Promise

```js
promise
  .then(result => console.log("success:", result))
  .catch(error => console.log("failure:", error.message))
  .finally(() => console.log("always runs, success or failure"));
```

- `.then(onFulfilled, onRejected)` — handles both outcomes; the second argument is optional.
- `.catch(onRejected)` — shorthand for `.then(undefined, onRejected)`.
- `.finally(fn)` — runs regardless of outcome, receives no argument, cannot change the result.

## 4. `.then()` Always Returns a NEW Promise

```js
const p1 = Promise.resolve(5);
const p2 = p1.then(n => n * 2);
console.log(p1 === p2); // false — .then() creates a brand-new Promise
```

This is the mechanism that makes **chaining** possible (full detail in the Promise Chaining topic) — each `.then()` returns a new Promise resolved with whatever its callback returns.

## 5. Static Helpers: `Promise.resolve` / `Promise.reject`

```js
Promise.resolve(42);              // an already-fulfilled Promise, value 42
Promise.reject(new Error("bad")); // an already-rejected Promise

// Useful for normalizing values that MIGHT already be Promises:
function ensurePromise(value) {
  return Promise.resolve(value); // wraps a plain value; if `value` is already a Promise, returns it as-is
}
```

## 6. Errors Thrown Inside `.then()` Become Rejections

```js
Promise.resolve(1)
  .then(() => { throw new Error("oops"); })
  .catch(e => console.log("caught:", e.message)); // "caught: oops"
```

A thrown error inside any `.then()` callback is automatically converted into a rejected Promise, which the next `.catch()` (or rejection handler) picks up — no manual `reject()` call needed inside `.then()`.

## 7. A Promise's Callback Always Runs Asynchronously

```js
console.log("1");
Promise.resolve().then(() => console.log("3"));
console.log("2");
// 1, 2, 3 — .then() callbacks NEVER run synchronously, even for an already-resolved Promise
```

This connects directly to the Microtasks vs Macrotasks topic — `.then()` callbacks are always scheduled as microtasks, never invoked immediately inline.

## Under the Hood: The Internal Slots Behind "Pending/Fulfilled/Rejected"

The spec defines a Promise as an object with several **internal slots** — not regular properties you can access, but real state the engine tracks per Promise instance. Understanding them explains exactly why `.then()` behaves the way it does.

| Internal slot | Holds |
|---|---|
| `[[PromiseState]]` | `"pending"`, `"fulfilled"`, or `"rejected"` |
| `[[PromiseResult]]` | the fulfillment value or rejection reason, once settled |
| `[[PromiseFulfillReactions]]` | a LIST of pending success-handler callbacks, waiting for fulfillment |
| `[[PromiseRejectReactions]]` | a LIST of pending failure-handler callbacks, waiting for rejection |

### What `.then()` actually does to these slots

```js
const p = new Promise(resolve => setTimeout(() => resolve(42), 1000));
p.then(value => console.log(value)); // called while p is still PENDING
```

Calling `.then()` on a still-pending Promise doesn't run anything yet — it **appends** a reaction record (your callback, plus the new Promise `.then()` itself returns) onto `[[PromiseFulfillReactions]]` and `[[PromiseRejectReactions]]`. Nothing executes until the Promise actually settles.

### What `resolve()`/`reject()` actually do

When `resolve(value)` finally runs, the engine: sets `[[PromiseState]]` to `"fulfilled"`, sets `[[PromiseResult]]` to `value`, then walks the entire `[[PromiseFulfillReactions]]` list and schedules **one microtask per reaction** (a "PromiseReactionJob") — this is the exact moment your `.then()` callbacks actually get queued for execution, not before. This is also why calling `.then()` on an *already-settled* Promise still queues a fresh microtask immediately (rather than running synchronously) — the reaction-scheduling code path is identical either way, whether the reaction was already waiting or just got added after the fact.

### Why a Promise can only ever settle once

`resolve`/`reject` themselves check `[[PromiseState]]` before doing anything — if it's no longer `"pending"`, the call is a silent no-op. This single guard is the entire mechanism behind "once settled, a Promise never changes state again" — it isn't a convention, it's enforced by a state check on every single resolve/reject invocation, including ones that "lose the race" (see the Promises Part 2 combinator behavior, and `Promise.all`'s internal counter in the Parallel vs Sequential topic).

Continue to `js_promises_theory_2.md` for `Promise.all`/`race`/`allSettled`/`any` and real-world patterns.
