Promise.all() รอหลาย Promise พร้อมกัน
เริ่มทั้งหมดพร้อมกัน รอจนเสร็จทุกตัว — เร็วกว่า chain เมื่องานไม่ต้องพึ่งกัน
Promises
ใช้ Promise.all() รอหลาย Promise พร้อมกัน เข้าใจ fail-fast behavior และทบทวนข้อผิดพลาดที่พบบ่อยทั้งบท Promises
ในหน้าที่แล้ว (Promise Chain) คุณเรียนการต่อ `.then()` เป็น chain — แต่ละขั้น **รอขั้นก่อนหน้าเสร็จก่อน** จึงเริ่มทำงาน แต่บางงาน **ไม่ต้องพึ่งกัน** — เช่น: - โหลด profile + โหลด posts → ไม่ต้องรอ profile เสร็จก่อนค่อยโหลด posts - โหลด config + โหลด translations → แยกกันทำได้ ถ้าใช้ chain → รอทีละอัน → **ช้าเปล่า ๆ** **`Promise.all([promise1, promise2, ...])`** แก้ปัญหานี้ — เริ่ม Promise ทุกตัว **พร้อมกัน** แล้วรอจนเสร็จทุกตัว: - รับ **array ของ Promise** - คืน Promise ตัวเดียวที่ resolve เมื่อ **ทุก Promise ใน array เสร็จ** - `.then()` callback ได้รับ **array ของผลลัพธ์**
chain รอทีละอัน (200ms) vs Promise.all เริ่มพร้อมกัน (~100ms)
function delay(ms, value) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(value);
}, ms);
});
}
// ❌ Chain — รอทีละอัน (ช้า)
delay(100, "profile")
.then(function (profile) {
console.log("chain:", profile);
return delay(100, "posts");
})
.then(function (posts) {
console.log("chain:", posts);
});
// รวม ~200ms — รอ profile เสร็จก่อนค่อยเริ่ม posts
// ✅ Promise.all — เริ่มพร้อมกัน (เร็วกว่า)
Promise.all([
delay(100, "profile"), // เริ่มทันที
delay(100, "posts"), // เริ่มทันที
]).then(function (results) {
console.log("all:", results[0], results[1]);
});
// รวม ~100ms — ทั้งสองเริ่มพร้อมกัน
// output (chain ช้ากว่า):
// all: profile posts
// chain: profile
// chain: postsสังเกตความต่าง: - **Chain**: `delay(100)` เสร็จก่อน → ค่อยเริ่ม `delay(100)` ตัวที่สอง → รวม **~200ms** - **Promise.all**: ทั้งสอง `delay(100)` **เริ่มพร้อมกัน** → รวม **~100ms** เวลารวมของ `Promise.all()` เท่ากับ Promise ที่ **ช้าที่สุด** — ไม่ใช่ผลรวมของทุกตัว เมื่อไหร่ใช้อะไร: - **Chain**: ขั้นถัดไปต้องใช้ค่าจากขั้นก่อนหน้า → รอตามลำดับ - **Promise.all**: งานไม่ต้องพึ่งกัน → เริ่มพร้อมกัน เร็วกว่า
Promise.all() เหมาะเมื่องานไม่ต้องพึ่งกัน
ถ้า step B ต้องใช้ค่าจาก step A → ใช้ chain. ถ้า A กับ B ทำแยกกันได้ → ใช้ Promise.all() เริ่มพร้อมกัน
กฎสำคัญของ `Promise.all()`: **ผลลัพธ์ออกมาเป็น array ที่ **ลำดับตรงกับลำดับที่ส่งเข้าไป** — ไม่ใช่ลำดับที่ Promise แต่ละตัวเสร็จ** ถ้าส่ง `[p1, p2, p3]` → `.then()` ได้ `[result1, result2, result3]` — แม้ p3 จะเสร็จก่อน p1 ก็ตาม
ส่ง delay(300), delay(100), delay(200) → ผลลัพธ์ยังออกตามลำดับ index 0, 1, 2
function delay(ms, value) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(value);
}, ms);
});
}
Promise.all([
delay(300, "A"), // ช้าที่สุด — แต่อยู่ index 0
delay(100, "B"), // เสร็จก่อนใคร — อยู่ index 1
delay(200, "C"), // เสร็จรองลงมา — อยู่ index 2
]).then(function (results) {
console.log("index 0:", results[0]); // A — ตามลำดับที่ส่งเข้าไป
console.log("index 1:", results[1]); // B
console.log("index 2:", results[2]); // C
});
// output (หลัง ~300ms):
// index 0: A
// index 1: B
// index 2: Cสังเกต: แม้ `delay(100, "B")` เสร็จก่อน `delay(300, "A")` — ผลลัพธ์ `B` ยังอยู่ที่ **index 1** เสมอ (ตามลำดับที่ส่งเข้าไป) การันตีคือ **ตำแหน่ง (position)** — index 0 = ผลของ Promise ตัวแรก, index 1 = ผลของ Promise ตัวที่สอง, เสมอ ทำให้ **destructuring** ปลอดภัย:
Promise.all() โหลด user, orders, settings พร้อมกัน — destructuring รับค่าได้เลย
function loadUser() {
return new Promise(function (resolve) {
resolve({ name: "Mali", level: 5 });
});
}
function loadOrders() {
return new Promise(function (resolve) {
resolve(["ออเดอร์ A", "ออเดอร์ B"]);
});
}
function loadSettings() {
return new Promise(function (resolve) {
resolve({ theme: "dark", lang: "th" });
});
}
Promise.all([loadUser(), loadOrders(), loadSettings()])
.then(function (results) {
var user = results[0]; // { name: "Mali", level: 5 }
var orders = results[1]; // ["ออเดอร์ A", "ออเดอร์ B"]
var settings = results[2]; // { theme: "dark", lang: "th" }
console.log("ผู้ใช้:", user.name);
console.log("ออเดอร์:", orders.length, "รายการ");
console.log("ธีม:", settings.theme);
});
// output:
// ผู้ใช้: Mali
// ออเดอร์: 2 รายการ
// ธีม: darkกฎข้อสำคัญที่สุดของ `Promise.all()`: **ถ้า Promise แม้แต่ตัวเดียวใน array reject → `Promise.all()` reject ทั้งชุดทันที** เรียกว่า **fail-fast** — เจอ error ตัวแรกก็ reject เลย ไม่รอให้ Promise ตัวอื่นเสร็จ: - `.then()` callback **ไม่ทำงาน** — ไม่มีผลลัพธ์ - `.catch()` callback **ทำงาน** — ได้รับ error ตัวแรกที่ reject
3 Promise: ตัวแรกสำเร็จ, ตัวที่สอง reject, ตัวที่สามสำเร็จ → ทั้งชุด reject
var p1 = Promise.resolve("A"); // สำเร็จ
var p2 = Promise.reject(new Error("B ล้มเหลว")); // reject
var p3 = Promise.resolve("C"); // สำเร็จ
Promise.all([p1, p2, p3])
.then(function (results) {
console.log("เสร็จหมด:", results); // ไม่ทำงาน — มี Promise reject
})
.catch(function (err) {
console.log("ดัก error:", err.message); // ดัก error: B ล้มเหลว
});
// output:
// ดัก error: B ล้มเหลวสังเกต: - `p1` และ `p3` สำเร็จ — แต่ `.then()` **ไม่ทำงาน** เพราะ `p2` reject - `.catch()` ดัก error จาก `p2` — ไม่ได้รับผลลัพธ์จาก `p1` หรือ `p3` **สิ่งสำคัญ: Promise อื่นยังรันอยู่** — `Promise.all()` แค่หยุดรอ ไม่ได้ยกเลิก Promise ที่กำลังทำงานอยู่
Promise อื่นยังรันอยู่ — ไม่ได้ถูกยกเลิก
Promise.all() หยุดแค่การรอ — Promise ที่กำลังทำงานอยู่ยังทำต่อ แค่ผลลัพธ์ไม่ถูกส่งต่อมาให้ .then()
ใส่ .catch() ต่อท้าย Promise.all() — ทั้งกรณีสำเร็จและล้มเหลวมีที่จบ
// กรณี 1: ทุก Promise สำเร็จ
Promise.all([
Promise.resolve("profile"),
Promise.resolve("posts"),
])
.then(function (results) {
console.log("สำเร็จ:", results[0], results[1]);
})
.catch(function (err) {
console.log("ล้มเหลว:", err.message);
});
// output:
// สำเร็จ: profile posts
// กรณี 2: มี Promise reject
Promise.all([
Promise.resolve("profile"),
Promise.reject(new Error("โหลด posts ไม่สำเร็จ")),
])
.then(function (results) {
console.log("สำเร็จ:", results[0], results[1]);
})
.catch(function (err) {
console.log("ล้มเหลว:", err.message);
});
// output:
// ล้มเหลว: โหลด posts ไม่สำเร็จบทนี้เป็นบทสุดท้ายของ Promises — มาทบทวนข้อผิดพลาดที่พบบ่อยที่สุดกัน ข้อผิดพลาดบางข้อเคยสอนในหน้าก่อนหน้าแล้ว (เช่น ลืม return, ไม่มี .catch()) — แต่มีอีกหลายข้อที่ยังไม่ได้พูดถึง:
Promise ถูกสร้างแต่ไม่มี .then() หรือ .catch() → ผลลัพธ์สูญหาย ไม่มีใครรู้
// ❌ Floating Promise — สร้างแล้วไม่ต่อ chain
function loadProfile() {
return new Promise(function (resolve) {
resolve({ name: "Mali" });
});
}
loadProfile(); // สร้าง Promise แต่ไม่ต่อ .then() → ผลลัพธ์หาย
// ไม่มี output — ไม่มีใครรับค่า
// ✅ แก้: ต่อ .then() เพื่อรับผลลัพธ์
loadProfile().then(function (profile) {
console.log("โหลดสำเร็จ:", profile.name);
});
// output:
// โหลดสำเร็จ: Mali**Floating Promise** คือ Promise ที่ถูกสร้างขึ้นแต่ **ไม่มีใครรอผล** — ไม่มี `.then()`, ไม่มี `.catch()`, ไม่ถูกส่งเข้า `Promise.all()` ผลลัพธ์ของ Promise นั้น **สูญหาย** — ถ้ามัน reject ก็จะเป็น **unhandled rejection** ด้วย **กฎ: ทุก Promise ที่สร้างต้องถูก chain, ส่งต่อ, หรือ return**
new Promise() แต่ executor ไม่มี resolve() หรือ reject() → Promise ค้าง pending ตลอดกาล
// ❌ Promise ค้าง pending — ลืมเรียก resolve
var stuck = new Promise(function (resolve, reject) {
// ลืมเรียก resolve() หรือ reject()
// → Promise อยู่ในสถานะ pending ตลอดกาล
});
console.log(stuck);
// Promise { <pending> } — ค้างอยู่แบบนี้ตลอด
stuck.then(function (value) {
console.log("ไม่มีวันทำงาน"); // ไม่มีวันถูกเรียก
});
// ✅ แก้: เรียก resolve หรือ reject ในทุก code path
var fixed = new Promise(function (resolve, reject) {
var success = true;
if (success) {
resolve("เสร็จแล้ว");
} else {
reject(new Error("ล้มเหลว"));
}
});
fixed.then(function (value) {
console.log(value); // เสร็จแล้ว
});Promise ค้าง pending เกิดได้ง่ายมาก — แม้แค่ลืมเรียก `resolve()` ในเงื่อนไขใดเงื่อนไขหนึ่ง: ``` new Promise(function (resolve, reject) { if (condition) { resolve(value); // เรียกตอน condition เป็น true } // ลืม reject ตอน condition เป็น false → pending! }); ``` **กฎ: executor ต้องเรียก resolve หรือ reject ในทุก code path**
ไม่จำเป็นต้อง new Promise() รอบ function ที่คืน Promise อยู่แล้ว — return ตรง ๆ ได้เลย
function getData() {
return Promise.resolve("ข้อมูล");
}
// ❌ Anti-pattern: ห่อ Promise ที่มีอยู่แล้ว
function bad() {
return new Promise(function (resolve, reject) {
getData()
.then(function (data) {
resolve(data); // ห่อ resolve ใน resolve — ไม่จำเป็น
})
.catch(function (err) {
reject(err); // ห่อ reject ใน reject — ไม่จำเป็น
});
});
}
// ✅ แก้: return Promise ตรง ๆ — สั้น ถูกต้อง อ่านง่าย
function good() {
return getData(); // return Promise เลย ไม่ต้องห่อ
}Anti-pattern นี้ไม่ได้ผิด syntax — แต่เพิ่มความซับซ้อนโดยไม่จำเป็น: - สร้าง Promise ซ้อน Promise → ยากอ่าน ยาก debug - ถ้าลืม `.catch()` ข้างใน → error หาย **กฎ: ถ้า function อยู่แล้วคืน Promise → return มันตรง ๆ ไม่ต้องห่อ**
ใช้ chain รอทีละอันทั้งที่งานไม่ต้องพึ่งกัน → ช้ากว่า Promise.all()
function delay(ms, value) {
return new Promise(function (resolve) {
setTimeout(function () { resolve(value); }, ms);
});
}
// ❌ Sequential — รอทีละอันทั้งที่ไม่ต้องพึ่งกัน (~300ms)
delay(100, "A").then(function (a) {
console.log(a);
return delay(100, "B");
}).then(function (b) {
console.log(b);
return delay(100, "C");
}).then(function (c) {
console.log(c);
});
// ✅ Promise.all — เริ่มพร้อมกัน (~100ms)
Promise.all([
delay(100, "A"),
delay(100, "B"),
delay(100, "C"),
]).then(function (results) {
console.log(results[0]);
console.log(results[1]);
console.log(results[2]);
});สรุปข้อผิดพลาดที่พบบ่อยทั้งหมดจากบท Promises:
| ข้อผิดพลาด | อาการ | วิธีแก้ |
|---|---|---|
| ลืม return ใน .then() | ค่ากลายเป็น undefined | เติม return ทุกครั้งที่ต้องส่งค่าต่อ |
| ไม่มี .catch() ใน chain | Unhandled rejection → warning หรือ crash | ทุก chain ที่อาจ reject ต้องมี .catch() |
| Floating Promise | สร้าง Promise แต่ไม่ต่อ chain → ผลลัพธ์สูญหาย | ทุก Promise ต้องถูก chain, ส่งต่อ, หรือ return |
| Promise ค้าง pending | executor ไม่เรียก resolve/reject → รอตลอดกาล | เรียก resolve หรือ reject ในทุก code path |
| Constructor anti-pattern | ห่อ Promise ที่มีอยู่แล้วใน new Promise() | return Promise ตรง ๆ ไม่ต้องห่อ |
| Sequential ไม่จำเป็น | ใช้ chain รอทีละอันทั้งที่งานไม่ต้องพึ่งกัน | ใช้ Promise.all() สำหรับงานอิสระ |
เริ่มทั้งหมดพร้อมกัน รอจนเสร็จทุกตัว — เร็วกว่า chain เมื่องานไม่ต้องพึ่งกัน
index 0 = ผลของ Promise ตัวแรกเสมอ — ไม่ใช่ตามลำดับที่เสร็จ
fail-fast — เจอ error ตัวแรกก็ reject เลย ไม่รอ Promise ตัวอื่น
อย่าใช้ chain สำหรับงานอิสระ — เสียเวลาเปล่า ๆ
ทุก Promise ที่สร้างต้องถูก chain, ส่งต่อ, หรือ return
ใส่ resolve หรือ reject ในทุก code path ของ executor
Unhandled rejection แสดง warning ใน console — ใน Node.js อาจทำให้ process หยุดทำงาน