C
JavaScript/Async/Lesson 16

Promise — *The Async Standard*

1 hr·theory
This chapter
2/3

Promise — *The Async Standard*

🎯 By the end of this lesson

After reading this lesson, you will be able to confidently do the following 3 things.

  • ✅ Pending → Fulfilled / Rejected state transitions
  • ✅ .then / .catch / .finally chaining
  • ✅ Differences between Promise.all · race · any · allSettled

Keep the learning objectives as a checklist, and close the lesson once you can answer all of them.

What is a Promise?

The Core in One Line

Promise = an object that says "there's no result right now, but I'll give it to you later." The standard for asynchronous work. A major advancement in ES6 (2015) that solved callback hell.

Callback Hell — The Old Way

javascript
// 🧪 Fake API — callback style (old Node.js standard)
const fetchUser   = (id,   cb) => setTimeout(() => cb(null, { id, name: 'Hong' }), 50);
const fetchOrders = (uid,  cb) => setTimeout(() => cb(null, [{ id: 1 }, { id: 2 }]), 50);
const fetchItems  = (oid,  cb) => setTimeout(() => cb(null, [{ name: 'book' }]), 50);
const handle = (err) => console.error('Failed:', err);
const id = 42;

fetchUser(id, (err, user) => {
    if (err) return handle(err);
    fetchOrders(user.id, (err, orders) => {
        if (err) return handle(err);
        fetchItems(orders[0].id, (err, items) => {
            if (err) return handle(err);
            console.log(items);   // [{ name: 'book' }]
            // Indentation increases infinitely *to the right* → callback hell
        });
    });
});

Callback inside callback inside callback... indentation grows like a pyramid. Callback hell.

The Promise Way

javascript
// 🧪 Fake API — Promise style (modern standard)
const wait = (ms) => new Promise(r => setTimeout(r, ms));
const fetchUser   = (id)  => wait(50).then(() => ({ id, name: 'Hong' }));
const fetchOrders = (uid) => wait(50).then(() => [{ id: 1 }, { id: 2 }]);
const fetchItems  = (oid) => wait(50).then(() => [{ name: 'book' }]);
const handle = (err) => console.error('Failed:', err);
const id = 42;

fetchUser(id)
    .then(user   => fetchOrders(user.id))
    .then(orders => fetchItems(orders[0].id))
    .then(items  => console.log(items))   // 📤 [{ name: 'book' }]
    .catch(err   => handle(err));

// 💡 Indentation of callback hell → horizontal chaining. Errors are caught once at the end.

Chaining makes it horizontal. Errors are handled with a single catch at the end.

The 3 States of a Promise

javascript
const success = true;
const value = 42;

const p = new Promise((resolve, reject) => {
    // 1️⃣ pending — when first created
    if (success) {
        resolve(value);   // 2️⃣-A fulfilled (success) → received by then
    } else {
        reject(new Error('Failure!'));  // 2️⃣-B rejected (failure) → received by catch
    }
});
p.then(v   => console.log('Success:', v))     // Runs on success
 .catch(err => console.error('Failure:', err)) // Runs on failure
 .finally(() => console.log('Done!'));         // Always runs regardless of success/failure

// 📤 Output:
//   Success: 42
//   Done!

Once fulfilled or rejected, the state cannot be changed.

Promises You'll Commonly Encounter

javascript
// fetch — standard HTTP
fetch('/api/users/42')
    .then(res => res.json())
    .then(data => console.log(data));
// Promise version of setTimeout
const wait = (ms) => new Promise(r => setTimeout(r, ms));
wait(1000).then(() => console.log("After 1 second"));

// Promise.resolve — already completed Promise
const p = Promise.resolve(42);
p.then(v => console.log(v));   // 42

Parallel — Promise.all

javascript
// 🧪 Fake API (copy-pasteable)
const wait = (ms) => new Promise(r => setTimeout(r, ms));
const fetchUser    = (id) => wait(50).then(() => ({ id, name: 'Hong' }));
const fetchOrders  = (id) => wait(50).then(() => [{ orderId: 1 }]);
const fetchProfile = (id) => wait(50).then(() => ({ bio: 'Developer' }));

(async () => {
    // 🏎️ Three requests start "simultaneously" → wait until all complete → receive results as an array
    const [user, orders, profile] = await Promise.all([
        fetchUser(42),     // Start time: 0ms
        fetchOrders(42),   // Start time: 0ms  ← Depart together!
        fetchProfile(42)   // Start time: 0ms
    ]);
    // End time: max(time taken for each request)

    console.log(user);     // { id: 42, name: 'Hong' }
    console.log(orders);   // [{ orderId: 1 }]
    console.log(profile);  // { bio: 'Developer' }
})();

All three requests start simultaneously → results are returned once all complete. 3x faster than sequential.

Note: If even one fails, the whole thing fails. If partial failure is acceptable, use Promise.allSettled:

javascript
const results = await Promise.allSettled([...]);
// [{status:'fulfilled', value:...}, {status:'rejected', reason:...}]

Promise.race — The Fastest One Wins

javascript
// 🧪 Fake response — fast (50ms) vs slow (200ms)
const wait = (ms, val) => new Promise(r => setTimeout(() => r(val), ms));

(async () => {
    // 🏁 race = competition. Only the "first one to finish" is received as a result
    const fastest = await Promise.race([
        wait(50,  'cached'),    // Fast → wins
        wait(200, 'origin'),    // Slow → loses
    ]);
    console.log(fastest);   // 'cached'   ← 50ms response first

    // 💡 Application — implementing timeout
    const withTimeout = (promise, ms) => Promise.race([
        promise,
        new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), ms))
    ]);

    try {
        const slow = wait(500, 'slow response');
        const res = await withTimeout(slow, 100);   // If no response within 100ms, timeout
        console.log(res);
    } catch (e) {
        console.error(e.message);   // "timeout"   ← 500 > 100, so timeout
    }
})();

Common Pitfalls

1. Missing catch:

javascript
fetch('/api').then(handle);   // ❌ Ignores errors
fetch('/api').then(handle).catch(err => log(err));   // ✅

An unhandled Promise rejection produces an Unhandled Promise Rejection warning. Always catch.

2. Nesting .then inside .then:

javascript
// ❌ Nested Promise
fetch('/api')
    .then(res => {
        res.json().then(data => console.log(data));
    });

// ✅ Chaining with return
fetch('/api')
    .then(res => res.json())
    .then(data => console.log(data));

3. Overusing .then instead of await:
These days, async/await is the standard. Use .then chaining only for simple cases.

Quick Summary

  • Promise = an object that promises a future result
  • then·catch·finally chaining
  • Promise.all for parallel execution
  • Modern code is cleaner with async/await

⚡ Try It Yourself — Promise Chaining + Promise.all

Compare sequential chaining and parallel Promise.all side by side.
✏️ JS 코드
📟 Console output
▶ Press the Run button
⚠️ Runs in a browser sandbox — only console.log() is supported; alert/fetch are not.

🤖 Try Asking AI This

Knowing the concepts from this lesson lets you give specific instructions to AI. Instead of a vague "fix this," make vocabulary-driven requests — that's the starting point for saving tokens.

  • "Convert this .then chain to async/await"
  • "Guard this code's unhandled rejection risks with .catch / try-catch"
  • "Parallelize these fetch calls with Promise.all"

Why Does This Save Tokens?

Without knowing the concepts, even after getting an AI answer you have to ask "what does that mean?" again. That follow-up question is what consumes tokens. Learn the concept once and the conversation ends in one round.

Read this first: Event Loop
Up next: Async/Await
Promise - JavaScript