Promise chain = ต่อ .then() หลายตัวเป็นลำดับ
แทน callback ซ้อนกัน ใช้ .then() ต่อเป็น chain อ่านจากบนลงล่างเหมือนโค้ด synchronous
Promises
ต่อ .then() เป็น chain — ส่งค่าปกติหรือ Promise ผ่าน chain, เข้าใจการไหลของค่าขั้นต่อขั้น และข้อควรระวังสำคัญอย่างการลืม return
ในหน้าที่แล้ว (catch() & finally()) คุณเห็นแล้วว่า `.then()` คืน Promise **ตัวใหม่** ทุกครั้ง — และ `.catch()` / `.finally()` ก็คืน Promise ตัวใหม่เหมือนกัน เพราะทุกอย่างใน chain คืน Promise → คุณสามารถ **ต่อ `.then()` หลายตัวเป็นลำดับ** ได้ — เรียกว่า **Promise chain** หน้านี้จะสอน: - **ต่อ `.then()`** ให้อ่านจากบนลงล่าง แทน callback ซ้อนกัน - **`return` ค่าปกติ** จาก `.then()` → ค่าส่งต่อไป `.then()` ถัดไป - **`return Promise`** จาก `.then()` → chain รอให้ Promise นั้นเสร็จก่อน - **ข้อควรระวังสำคัญ**: การลืม `return` ที่ทำให้ค่าหายกลางทาง
callback ซ้อนกันลึกขึ้นเรื่อย ๆ — Promise chain อ่านจากบนลงล่างแทน
// ❌ Callback hell — ซ้อนกัน ยากอ่าน ยากแก้
loadUser(function (user) {
loadOrders(user.id, function (orders) {
showResult(user, orders);
});
});
// ✅ Promise chain — ตรง อ่านง่าย แก้ง่าย
loadUser()
.then(function (user) {
return loadOrders(user.id);
})
.then(function (orders) {
console.log("โหลดเสร็จ:", orders.length, "รายการ");
});
// output:
// โหลดเสร็จ: 3 รายการสังเกตความต่าง: - **Callback**: ขั้นตอนถัดไปซ้อนอยู่ข้างในขั้นก่อนหน้า — ยิ่งมีหลายขั้น ยิ่งซ้อนลึก (callback hell) - **Promise chain**: ขั้นตอนทุกขั้นอยู่ระดับเดียวกัน — อ่านจากบนลงล่างเหมือนโค้ดปกติ ทั้งสองทำงานเหมือนกัน — ต่างกันที่ **รูปแบบการเขียน** เท่านั้น
Promise chain อ่านเหมือนโค้ดปกติ
แต่ละ .then() คือขั้นตอนถัดไป — อ่านจากบนลงล่างเหมือนโค้ด synchronous ทุกประการ แต่ละขั้นรอขั้นก่อนหน้าเสร็จก่อนจึงเริ่มทำงาน
กฎข้อแรกของ Promise chain: **เมื่อ `.then()` callback `return` ค่าปกติ (string, number, object) → JavaScript ห่อค่านั้นใน `Promise.resolve(returnValue)` อัตโนมัติ → `.then()` ถัดไปรับค่านั้นเป็น parameter** เรียกง่าย ๆ: **return อะไร → `.then()` ถัดไปได้อะไร**
return 200 จาก .then() ตัวแรก → .then() ตัวถัดไปรับ 200 เป็น parameter
Promise.resolve(100)
.then(function (n) {
return n * 2; // return 200 → ส่งต่อ
})
.then(function (result) {
console.log("ได้รับ:", result); // ได้รับ: 200
});
// output:
// ได้รับ: 200ลำดับการทำงาน: 1. `Promise.resolve(100)` → fulfilled ด้วย `100` 2. `.then()` ตัวแรก callback รับ `100` → `return 100 * 2` → คืน Promise ใหม่ที่ fulfilled ด้วย `200` 3. `.then()` ตัวถัดไป callback รับ `200` → log "ได้รับ: 200" **ค่าธรรมดาทุกประเภทส่งต่อได้** — ไม่ใช่แค่ number:
รับ object เข้ามา → คำนวณ → return object ใหม่ → .then() ถัดไปใช้ได้เลย
Promise.resolve({ price: 50, qty: 4 })
.then(function (item) {
var total = item.price * item.qty;
return { item: item, total: total }; // return object ใหม่
})
.then(function (result) {
console.log("ราคารวม:", result.total);
console.log("จำนวน:", result.item.qty);
});
// output:
// ราคารวม: 200
// จำนวน: 4สังเกต: `.then()` ตัวแรกรับ `{ price: 50, qty: 4 }` → คำนวณ total → `return` object ใหม่ `{ item, total }` → `.then()` ตัวถัดไปรับ object ใหม่นั้น **กฎง่าย ๆ: return อะไร ค่านั้นไหลไป `.then()` ถัดไป**
กฎข้อที่สองของ Promise chain: **เมื่อ `.then()` callback `return Promise` (แทนค่าปกติ) → JavaScript ไม่ได้ห่อซ้อนอีกที — แต่ **รอ** ให้ Promise ตัวนั้น settle ก่อน แล้วค่อยส่งค่าที่ resolve ไปยัง `.then()` ถัดไป** เรียกง่าย ๆ: **return Promise → chain รอให้เสร็จก่อนแล้วค่อยทำขั้นถัดไป** นี่คือจุดที่ทำให้ chain สามารถต่องาน async หลายขั้นตอนได้ — ขั้นตอนถัดไปรอขั้นก่อนหน้าเสร็จก่อนเสมอ
delay() return Promise — chain รอ delay ตัวแรกเสร็จก่อน ค่อยเริ่ม delay ตัวถัดไป
function delay(ms) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve("ผ่านไป " + ms + "ms");
}, ms);
});
}
delay(500) // รอ 500ms
.then(function (msg) {
console.log("ขั้น 1:", msg);
return delay(300); // return Promise → chain รอ
})
.then(function (msg) {
console.log("ขั้น 2:", msg);
return "จบแล้ว"; // return ค่าปกติ
})
.then(function (msg) {
console.log("ขั้น 3:", msg);
});
// output (หลัง 500ms):
// ขั้น 1: ผ่านไป 500ms
// (รออีก 300ms)
// ขั้น 2: ผ่านไป 300ms
// ขั้น 3: จบแล้วลำดับการทำงาน: 1. `delay(500)` → รอ 500ms → resolve "ผ่านไป 500ms" 2. `.then()` ตัวแรก → log "ขั้น 1" → `return delay(300)` → **chain รอ** 300ms 3. `.then()` ตัวถัดไป → log "ขั้น 2" → `return "จบแล้ว"` → ค่าปกติ ส่งต่อเลย 4. `.then()` สุดท้าย → log "ขั้น 3" **รวมเวลา: 500 + 300 = 800ms** — chain ทำทีละขั้น รอขั้นก่อนหน้าเสร็จก่อนเสมอ
getUser() return Promise → .then() return getOrders() → chain รอโหลดออเดอร์เสร็จก่อนทำขั้นถัดไป
function getUser() {
return new Promise(function (resolve) {
resolve({ id: 42, name: "Mali" });
});
}
function getOrders(userId) {
return new Promise(function (resolve) {
resolve(["ออเดอร์ A", "ออเดอร์ B"]);
});
}
getUser()
.then(function (user) {
console.log("ผู้ใช้:", user.name);
return getOrders(user.id); // return Promise → chain รอ
})
.then(function (orders) {
console.log("ออเดอร์:", orders.length, "รายการ");
});
// output:
// ผู้ใช้: Mali
// ออเดอร์: 2 รายการสังเกต: - `.then()` ตัวแรกรับ `user` object → log ชื่อ → `return getOrders(user.id)` (เป็น Promise) - **chain รอ** `getOrders` resolve แล้วค่อยส่งค่าต่อ - `.then()` ตัวถัดไปรับ `orders` array → log จำนวน ทั้งสองขั้นตอนเป็น async — แต่ chain ทำให้อ่านเหมือนเขียนทีละบรรทัด **return ค่าปกติ vs return Promise — ต่างกันตรงไหน?**: - `return 100` → ค่าส่งต่อทันที (ไม่รอ) - `return Promise.resolve(100)` → **รอ** Promise นั้น resolve แล้วค่อยส่ง `100` ต่อ - ผลลัพธ์ออกมาเหมือนกัน (`.then()` ถัดไปได้ `100`) — แต่ return Promise ทำให้ chain รองาน async เสร็จก่อน
ก่อนหน้านี้คุณเห็นตัวอย่างค่าไหลผ่าน chain หลายแบบแล้ว — ถึงเวลาเดินดูทีละ step เพื่อให้เห็นภาพชัด ๆ กฎการไหลของค่า: - `.then()` แต่ละตัว **รับค่า** จาก `return` ของขั้นก่อนหน้า - `return` ค่าปกติ → ค่านั้นไหลต่อไปทันที - `return` Promise → resolved value ของ Promise นั้นไหลต่อไป ลองเดินทีละ step กับตัวอย่างนี้:
สิ่งที่ trace แสดง: 1. **Step 1**: `Promise.resolve(80)` → fulfilled ด้วย `80` 2. **Step 2**: `.then()` รับ `80` → คำนวณ `80 + 20 = 100` → log "ได้โบนัส: 100" → **return `100`** 3. **Step 3**: `.then()` รับ `100` → สร้าง string `"คะแนนรวม: 100"` → **return string นั้น** 4. **Step 4**: `.then()` รับ `"คะแนนรวม: 100"` → log สังเกต: **output ของแต่ละขั้น = input ของขั้นถัดไป** — ค่าไหลผ่าน chain แบบ pipeline ถ้าขั้นไหนลืม `return` → ค่าจะกลายเป็น `undefined` → ขั้นถัดไปรับ `undefined` → chain พัง — หัวข้อถัดไปจะสอนเรื่องนี้อย่างละเอียด
ข้อผิดพลาดที่พบบ่อย **ที่สุด** ใน Promise chain คือ **ลืม `return`** เมื่อ `.then()` callback ไม่มี `return` → JavaScript return `undefined` โดยปริยาย → `.then()` ถัดไปรับ `undefined` — **ค่าจากขั้นก่อนหน้าหายกลางทาง** ทั้ง chain จะพังตั้งแต่จุดที่ลืม `return` เป็นต้นไป
getScore() รันแต่ค่าไม่ถูกส่งต่อ เพราะลืม return → .then() ถัดไปได้ undefined
// ❌ ลืม return — getScore() รันแต่ค่าไม่ส่งต่อ
function getScore(userId) {
return Promise.resolve(85);
}
Promise.resolve({ id: 1 })
.then(function (user) {
getScore(user.id); // ลืม return!
})
.then(function (score) {
console.log("คะแนน:", score); // คะแนน: undefined
});
// output:
// คะแนน: undefinedเติม return หน้า getScore() → Promise ที่ getScore() คืนถูกส่งต่อใน chain → .then() ถัดไปได้ 85
// ✅ มี return — getScore() รันและค่าส่งต่อ
function getScore(userId) {
return Promise.resolve(85);
}
Promise.resolve({ id: 1 })
.then(function (user) {
return getScore(user.id); // มี return!
})
.then(function (score) {
console.log("คะแนน:", score); // คะแนน: 85
});
// output:
// คะแนน: 85ลืม return = ค่ากลายเป็น undefined
ทุกครั้งที่ .then() callback ไม่มี return ค่าที่ออกมาคือ undefined เสมอ — ทั้ง chain จะพังตั้งแต่จุดนั้น เพราะขั้นถัดไปรับ undefined แทนค่าที่ต้องการ
คำนวณ total ใน .then() แต่ลืม return → .then() ถัดไปได้ undefined
// ❌ ลืม return ตอนแปลงค่า
Promise.resolve({ price: 100, qty: 3 })
.then(function (item) {
var total = item.price * item.qty; // คำนวณแล้ว
// ลืม return total!
})
.then(function (result) {
console.log("รวม:", result); // รวม: undefined
});
// output:
// รวม: undefined
// ✅ แก้: เติม return
Promise.resolve({ price: 100, qty: 3 })
.then(function (item) {
var total = item.price * item.qty;
return total; // เติม return
})
.then(function (result) {
console.log("รวม:", result); // รวม: 300
});
// output:
// รวม: 300สรุปสถานการณ์ต่าง ๆ ของค่าที่ส่งต่อใน chain:
| สถานการณ์ | โค้ดใน .then() | ค่าที่ส่งต่อ |
|---|---|---|
| return ค่าปกติ | return 100 | 100 |
| return Promise | return Promise.resolve(100) | 100 (รอให้ resolve ก่อน) |
| ไม่มี return | doSomething() | undefined |
| return undefined | return undefined | undefined |
แทน callback ซ้อนกัน ใช้ .then() ต่อเป็น chain อ่านจากบนลงล่างเหมือนโค้ด synchronous
JavaScript ห่อค่าใน Promise.resolve() อัตโนมัติ — .then() ถัดไปรับค่านั้นเป็น parameter
ถ้า return Promise จาก .then() chain จะรอ Promise นั้นเสร็จก่อนแล้วค่อยส่ง resolved value ต่อ
ทุก .then() callback ที่ไม่มี return จะส่ง undefined ไปยัง .then() ถัดไป — chain พังตั้งแต่จุดนั้น
parameter ของ .then() callback มาจาก return ของขั้นก่อนหน้าเสมอ — ไม่ได้เข้าถึงขั้นที่ 2 ข้ามไปขั้นที่ 5
แต่ละ .then() รอขั้นก่อนหน้าเสร็จก่อน จึงเริ่มทำงาน — เหมาะสำหรับงานที่ต้องทำตามลำดับ