# JavaScript Promises 101 — A Practical, No‑Nonsense Guide

##   
1) What is a Promise?

A **Promise** represents the eventual *result* of an asynchronous operation.

**States**

* **pending** → operation in progress
    
* **fulfilled** → operation completed successfully (has a value)
    
* **rejected** → operation failed (has a reason/error)
    

**Lifecycle**

```css
Pending  →  Fulfilled (value)  →  Rejected  (error)
```

**Key idea:** A Promise is a *container* for a future value. It does not execute work by itself—work starts when you call the async function or API that returns it (e.g., `fetch()`).

---

## 2) Where does the work actually happen?

* In the **browser**: network stack, timers, Web APIs
    
* In **Node.js**: libuv (thread pool), OS syscalls, timers
    

> The **JavaScript thread** doesn’t perform the I/O. It **waits to be notified** when the work completes and then processes the completion via the **microtask queue**.

---

## 3) `async` functions and `await`

```js
async function example() {
  // Synchronous until the first await
  const data = await fetch('/api'); // pauses here until the promise settles
  return data; // returned value becomes the resolution value of the async function
}
```

* An `async` function always returns a Promise.
    
* **Execution runs synchronously** until it hits the first `await`.
    
* At `await`, the function **pauses**, returning a **pending Promise** to the caller.
    
* When the awaited promise settles, the function **resumes** after the `await`.
    

> `await` doesn’t make the work happen—it **waits for and retrieves the result** of work already in progress.

---

## 4) Await *inside* vs awaiting the *outer function*

```javascript
async function doSomething() {
  await fetch('/api/data'); // awaits inside
  console.log('done');      // runs later when fetch resolves
}

doSomething();             // caller is NOT awaiting
console.log('other work');
// output: "other work" then "done"
```

* `await` **inside** controls sequencing **inside** the function.
    
* Awaiting the **outer** function controls whether the **caller** waits for completion / gets the returned value.
    

> Inside awaits still run even if the caller never awaits the outer function (but beware unhandled rejections; see §10).

---

## 5) When should you `await`?

**Await when**

* You **need the value**:
    
    ```javascript
    const user = await fetchUser();
    render(user);
    ```
    
* You need **ordering/atomicity** (e.g., show a loader, then hide it only after completion):
    
    ```javascript
    setLoading(true);
    await save();
    setLoading(false);
    ```
    
* You must **handle errors** with `try/catch` at this call site.
    

**Don’t await when**

* **Fire-and-forget** side effects (analytics, logging):
    
    ```javascript
    logEvent().catch(console.error); // at least catch errors
    ```
    
* You want to run tasks **in parallel**:
    
    ```javascript
    const p1 = fetchA();
    const p2 = fetchB();
    const [a, b] = await Promise.all([p1, p2]);
    ```
    

---

## 6) Parallel vs Sequential

**Sequential (slower)**

```javascript
await task1();
await task2();
```

**Parallel (faster)**

```javascript
const p1 = task1(); // start both
const p2 = task2();
await Promise.all([p1, p2]);
```

**Handling partial failures**

```javascript
const results = await Promise.allSettled([task1(), task2()]);
for (const r of results) {
  if (r.status === 'fulfilled') console.log(r.value);
  else console.error(r.reason);
}
```

---

## 7) Request coalescing (de-dup) pattern

Avoid duplicate in-flight requests for the same key/id.

```javascript
class Loader<T> {
  private pending = new Map<string, Promise<T>>();
  private cache = new Map<string, T>();

  async get(id: string): Promise<T> {
    if (this.cache.has(id)) return this.cache.get(id)!;
    const p = this.pending.get(id);
    if (p) return p; // reuse in-flight

    const request = (async () => {
      try {
        const value = await fetchThing(id);
        this.cache.set(id, value);
        return value;
      } finally {
        this.pending.delete(id); // runs on success or failure
      }
    })();

    this.pending.set(id, request);
    return request;
  }
}
```

**Notes**

* Async IIFE returns a promise **immediately**; actual work continues in background.
    
* `finally` clears the in-flight marker regardless of outcome.
    

---

## 8) Error handling

`try`/`catch` with `await`

```javascript
try {
  const data = await fetchJSON(url);
} catch (err) {
  // handle / show toast / retry
}
```

`.then/.catch`

```javascript
fetchJSON(url)
  .then(data => use(data))
  .catch(err => handle(err));
```

**Top-level (fire-and-forget)**

```javascript
doSideEffect().catch(err => console.error('background failed', err));
```

---

## 9) Unhandled promise rejections

If a promise **rejects** and nothing awaits or `.catch()`es it:

* **Browsers**: console warning
    
* **Node.js**: unhandled rejection warning; newer Node can **terminate** the process based on `unhandledRejection` behavior
    

**Always attach a catch** for fire-and-forget operations:

```js
void doSomethingAsync().catch(reportError);
```

---

## 10) Cancellation & timeouts

Promises can’t be canceled intrinsically, but many APIs support **AbortController**.

```javascript
const c = new AbortController();
const p = fetch(url, { signal: c.signal });

setTimeout(() => c.abort(), 3000); // cancel after 3s

try {
  const res = await p;
} catch (e) {
  if (e.name === 'AbortError') console.log('aborted');
}
```

**Timeout utility**

```javascript
function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), ms))
  ]);
}
```

---

## 11) Microtasks vs Macrotasks (event loop essentials)

* **Microtasks**: promise reactions (`.then`, `await` continuations)
    
* **Macrotasks**: `setTimeout`, `setInterval`, DOM events
    

**Order**: After each macrotask tick, the engine drains **all microtasks** before the next macrotask.

```javascript
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('microtask'));
// output: microtask, then timeout
```

---

## 12) Common pitfalls & fixes

* **Forgetting to handle rejections**
    
    * *Fix*: always add `.catch(...)` on fire-and-forget, or wrap in `try/catch`.
        
* **Accidentally running sequentially** when you intended parallelism
    
    * *Fix*: start tasks first, `await` with `Promise.all`.
        
* **Mutating shared results from cache**
    
    * *Fix*: return a copy or `Object.freeze` immutable results.
        
* **Shallow vs deep clone**
    
    * Use `structuredClone` when needed; spreading is shallow and strips prototypes.
        
* **Returning before cleanup**
    
    * Use `finally` to ensure cleanup always runs.
        

---

## 13) Patterns cheat‑sheet

**Start now, await later**

```javascript
const work = doWork();
// ... do other things
const result = await work;
```

**Fire and forget (with error logging)**

```javascript
void doSideEffect().catch(console.error);
```

**Serialize with a mutex-like queue**

```javascript
let last = Promise.resolve();
function enqueue(task) {
  const run = last.then(task, task);
  last = run.catch(() => {});
  return run;
}
```

**Rate limit calls** (token bucket / debounce + promises) — out of scope for brevity but good to know.

---

## 14) FAQ

**Q: If I don’t** `await` a returned promise, does the work still happen?  
A: Yes. The work continues in the background. But handle rejections to avoid unhandled warnings.

**Q: Why do I need to** `await` later if the promise already resolved?  
A: To *retrieve* the resolved value and to ensure your code runs **after** completion. Without `await`, you only have the Promise, not its value.

**Q: Does** `await` block the thread?  
A: No. It pauses *that async function* and yields control to the event loop.

**Q: Is** `await` the same as `.then`?  
A: Semantically similar. `await` is syntax sugar for chaining `.then` and handling errors via `try/catch`.

**Q: Will** `finally` run if a promise rejects?  
A: Yes—`finally` always runs.

---

## 15) Minimal mental model

1. Start async work → you get a **Promise** immediately.
    
2. Work proceeds **outside** the JS thread.
    
3. When done, JS schedules a **microtask** to resume your code.
    
4. Use `await`/`.then` to: **(a)** get the value, **(b)** sequence dependent code, **(c)** handle errors.
    

---

## 16) Mini-lab: log the timeline

```javascript
async function demo() {
  console.log('A: start');
  const p = fetch('/api/slow');
  console.log('B: after starting fetch');
  const res = await p;           // pauses here
  console.log('C: after await');
  const json = await res.json();
  console.log('D:', json);
}

demo();
console.log('E: outside');
// Possible order: A, B, E, C, D
```

Use this to verify your understanding of ordering.

---

## 17) Production tips

* Prefer `Promise.all` for independent async work.
    
* Always **catch** background promises.
    
* Centralize **error reporting** for async failures.
    
* Consider **timeouts** and **cancellation** for network requests.
    
* Use `finally` for cleanup (spinners, locks, in-flight maps).
    

---

**You’ve got this.** Promises are just: *start work → get a token (promise) → resume when it’s done.* Master the timing, and the rest follows naturally.
