Promise คืออะไร – 30 เมษายน 2569

ทำความเข้าใจพื้นฐาน Promise: อนาคตของการจัดการ Asynchronous ใน JavaScript

Promise คืออะไร

Photo by Nemuel Sereti on Pexels

ในการเขียนโปรแกรมด้วย JavaScript ปัญหาที่นักพัฒนาต้องเผชิญอยู่บ่อยครั้งคือการจัดการกับงานที่ต้องใช้เวลาในการประมวลผล (Asynchronous Operations) เช่น การดึงข้อมูลจาก API การอ่านไฟล์ หรือการตั้งเวลาทำงาน เมื่อก่อนเรามักจะใช้ “Callback Functions” ในการจัดการเรื่องนี้ แต่เมื่อระบบมีความซับซ้อนขึ้น การซ้อนกันของ Callback หลายชั้นจะนำไปสู่ปัญหาที่เรียกว่า Callback Hell ซึ่งทำให้โค้ดอ่านยากและดูแลรักษาได้ลำบากมาก

Promise ถูกนำเข้ามาในมาตรฐาน ES6 เพื่อแก้ปัญหานี้โดยเฉพาะ โดยมันเปรียบเสมือน “คำสัญญา” ว่าในอนาคตเราจะได้รับผลลัพธ์บางอย่างกลับมา ไม่ว่าผลลัพธ์นั้นจะเป็นข้อมูลที่สำเร็จ (Success) หรือข้อผิดพลาด (Error) ก็ตาม การใช้ Promise ช่วยให้เราสามารถเขียนโค้ดที่ดูเป็นลำดับขั้นตอน (Sequential) มากขึ้น และแยกส่วนการจัดการผลลัพธ์กับส่วนการจัดการข้อผิดพลาดออกจากกันได้อย่างชัดเจน

สถานะทั้ง 3 ของ Promise ที่คุณต้องรู้

ก่อนจะเริ่มเขียนโค้ด สิ่งสำคัญคือต้องเข้าใจว่า Promise มีสถานะ (State) ที่เป็นไปได้เพียง 3 สถานะเท่านั้นคือ Pending (กำลังรอคอย), Fulfilled (ทำงานสำเร็จ) และ Rejected (ทำงานไม่สำเร็จ) เมื่อ Promise เปลี่ยนสถานะจาก Pending ไปเป็นอย่างอื่นแล้ว จะไม่สามารถเปลี่ยนกลับมาหรือเปลี่ยนไปเป็นสถานะอื่นได้อีกเลย ซึ่งช่วยให้การคาดการณ์พฤติกรรมของโปรแกรมทำได้แม่นยำ

โครงสร้างและการสร้าง Promise Object ด้วยตัวเอง

การสร้าง Promise เริ่มต้นด้วยการใช้ Keyword new Promise() ซึ่งภายในจะรับฟังก์ชันที่เรียกว่า “Executor” โดยฟังก์ชันนี้จะมีพารามิเตอร์สองตัวคือ resolve และ reject ซึ่งเราจะเป็นคนกำหนดเองว่าเมื่อไหร่ที่งานนั้นถือว่าสำเร็จ และเมื่อไหร่ที่ถือว่าล้มเหลว กระบวนการนี้ช่วยให้เราสามารถห่อหุ้ม (Encapsulate) โค้ดที่เป็นแบบ Asynchronous เดิมๆ ให้กลายเป็นรูปแบบ Promise ที่ทันสมัยได้

เมื่อเราเรียกใช้ resolve(value) สถานะของ Promise จะเปลี่ยนเป็น Fulfilled และส่งค่า value นั้นไปยังผู้รับ แต่ถ้าเกิดข้อผิดพลาดหรือเราเรียก reject(error) สถานะจะเปลี่ยนเป็น Rejected ทันที การออกแบบเช่นนี้ทำให้เราสามารถควบคุม Flow ของข้อมูลได้ตั้งแต่ต้นทางจนถึงปลายทาง โดยไม่ต้องกังวลว่าข้อมูลจะสูญหายระหว่างทางหากมีการจัดการที่ดีพอ

ตัวอย่างการสร้าง Promise สำหรับการจำลองโหลดข้อมูล


const fetchData = new Promise((resolve, reject) => {
    const success = true;
    console.log("กำลังโหลดข้อมูล...");

    setTimeout(() => {
        if (success) {
            resolve({ id: 1, name: "Somsak", status: "Active" });
        } else {
            reject("เกิดข้อผิดพลาด: ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ได้");
        }
    }, 2000);
});

fetchData
    .then((data) => {
        console.log("ดึงข้อมูลสำเร็จ:", data);
    })
    .catch((error) => {
        console.error(error);
    })
    .finally(() => {
        console.log("สิ้นสุดการทำงาน");
    });

การใช้งาน .then(), .catch() และ .finally() เพื่อควบคุม Flow

หลังจากที่เราสร้าง Promise ขึ้นมาแล้ว ขั้นตอนต่อไปคือการนำค่านั้นมาใช้งานผ่าน Method หลัก 3 ตัว เริ่มจาก .then() ซึ่งจะทำงานเมื่อ Promise ถูก Resolve ค่าออกมา เราสามารถส่งข้อมูลต่อกันเป็นทอดๆ (Chaining) ได้ผ่าน Method นี้ ทำให้การประมวลผลข้อมูลหลายขั้นตอนดูสะอาดตาและเป็นระเบียบมากกว่าการเขียน Callback ซ้อนกัน

ส่วน .catch() คือหัวใจสำคัญของการจัดการ Error หากเกิดปัญหาขึ้นที่จุดใดจุดหนึ่งใน Promise Chain ตัว Error จะถูกส่งข้ามขั้นตอนอื่นๆ มาที่ catch ทันที ทำให้เราไม่ต้องเขียนเช็ค Error ในทุกๆ บรรทัด และปิดท้ายด้วย .finally() ซึ่งจะทำงานเสมอไม่ว่าผลลัพธ์จะออกมาเป็นอย่างไร เหมาะสำหรับการสั่งปิด Loading Spinner หรือการคืนค่าทรัพยากรต่างๆ

ทำไมการใช้ Promise Chaining ถึงดีกว่า Callback?

การทำ Promise Chaining ช่วยให้เราอ่านโค้ดจากบนลงล่างเหมือนการอ่านหนังสือ แทนที่จะต้องอ่านแบบลึกเข้าไปข้างในเรื่อยๆ นอกจากนี้ยังช่วยลดความซ้ำซ้อนของโค้ดจัดการ Error เพราะ catch เพียงจุดเดียวสามารถดักจับข้อผิดพลาดที่เกิดขึ้นจากทุกๆ then ที่อยู่ก่อนหน้ามันได้ ช่วยลดโอกาสการเกิด Bug ที่เกิดจากการลืมดักจับ Error ในบางจุด

การจัดการ Promise หลายตัวพร้อมกันด้วย Promise.all และ Promise.race

ในแอปพลิเคชันระดับมืออาชีพ บ่อยครั้งที่เราต้องดึงข้อมูลจากหลายแหล่งพร้อมกัน เช่น ดึงข้อมูลโปรไฟล์ผู้ใช้พร้อมกับรายการสินค้า หากเราดึงทีละอย่างจะทำให้แอปพลิเคชันทำงานช้าลง JavaScript จึงมี Method อย่าง Promise.all() ที่รับ Array ของ Promise และจะรอให้ทุกตัวทำงานสำเร็จก่อนจึงจะคืนค่าออกมาเป็น Array ของผลลัพธ์ทั้งหมด

อย่างไรก็ตาม Promise.all() มีข้อควรระวังคือ หากมี Promise ตัวใดตัวหนึ่งในกลุ่มเกิด Reject ขึ้นมา ทุกตัวที่เหลือจะถูกยกเลิกผลลัพธ์ทันที หากเราต้องการพฤติกรรมที่ต่างออกไป เช่น ต้องการแค่ตัวที่ทำงานเสร็จเร็วที่สุด เราสามารถใช้ Promise.race() ได้ ซึ่งจะมีประโยชน์มากในกรณีการตั้ง Time-out ให้กับการดึงข้อมูลจาก Network ที่อาจจะช้าเกินไป

ตัวอย่างการใช้งาน Promise.all ในสถานการณ์จริง


const getProfile = new Promise(resolve => setTimeout(() => resolve("User: Somsak"), 1000));
const getPosts = new Promise(resolve => setTimeout(() => resolve(["Post 1", "Post 2"]), 1500));
const getFriends = new Promise(resolve => setTimeout(() => resolve(50), 500));

Promise.all([getProfile, getPosts, getFriends])
    .then((results) => {
        const [user, posts, friendsCount] = results;
        console.log("--- สรุปข้อมูล ---");
        console.log(user);
        console.log("จำนวนโพสต์:", posts.length);
        console.log("จำนวนเพื่อน:", friendsCount);
    })
    .catch((err) => {
        console.error("มีบางอย่างผิดพลาด:", err);
    });

จาก Promise สู่ Async/Await: วิวัฒนาการที่ทำให้โค้ดสวยงามขึ้น

แม้ว่า Promise จะดีกว่า Callback มาก แต่การใช้ .then() ต่อกันยาวๆ ก็อาจจะยังทำให้โค้ดดูซับซ้อนในกรณีที่มี Logic เยอะๆ ในปี 2017 จึงมีการเปิดตัว async/await ซึ่งเป็น “Syntactic Sugar” ที่สร้างอยู่บนพื้นฐานของ Promise มันช่วยให้เราเขียนโค้ด Asynchronous ให้มีหน้าตาเหมือนโค้ด Synchronous ปกติ ทำให้การอ่านและทำความเข้าใจ Logic ของโปรแกรมทำได้ง่ายขึ้นอย่างมหาศาล

เมื่อเราเติมคำว่า async หน้าฟังก์ชัน ฟังก์ชันนั้นจะคืนค่าเป็น Promise เสมอ และเราสามารถใช้ await ภายในฟังก์ชันนั้นเพื่อรอให้ Promise ทำงานเสร็จก่อนจะไปบรรทัดถัดไป การจัดการ Error ในรูปแบบนี้จะเปลี่ยนจากการใช้ .catch() มาเป็นการใช้บล็อก try...catch แบบมาตรฐาน ซึ่งเป็นรูปแบบที่นักพัฒนาส่วนใหญ่คุ้นเคยอยู่แล้ว

ข้อดีของการเปลี่ยนมาใช้ Async/Await

  • อ่านง่าย: โค้ดดูสะอาดตา ไม่มีการซ้อนกันของวงเล็บและเครื่องหมายปีกกาที่ซับซ้อน
  • Debugging ง่ายขึ้น: เมื่อเกิด Error ตัว Stack Trace จะระบุบรรทัดที่เกิดปัญหาได้แม่นยำกว่าการใช้ Promise Chain
  • การจัดการ Logic: สามารถใช้ if-else หรือ loop ร่วมกับ await ได้อย่างเป็นธรรมชาติ
  • การดักจับ Error: ใช้ try/catch ครอบส่วนที่ต้องการจัดการ Error ได้อย่างครอบคลุม

สรุป

Promise คือเครื่องมือที่ขาดไม่ได้สำหรับการพัฒนาเว็บแอปพลิเคชันในปัจจุบัน มันเปลี่ยนวิธีการจัดการงานที่เป็น Asynchronous จากความสับสนของ Callback ให้กลายเป็นระบบที่คาดเดาได้และจัดการง่าย การเข้าใจสถานะ Pending, Fulfilled และ Rejected รวมถึงการใช้ Method อย่าง .then() และ .catch() จะเป็นรากฐานสำคัญที่ทำให้คุณสามารถต่อยอดไปสู่การใช้งาน Async/Await ได้อย่างมีประสิทธิภาพ

การเลือกใช้ Promise อย่างถูกต้อง ไม่เพียงแต่จะทำให้โค้ดของคุณทำงานได้ดีขึ้น แต่ยังส่งผลต่อความสามารถในการบำรุงรักษาโค้ดในระยะยาว (Maintainability) และช่วยให้เพื่อนร่วมทีมคนอื่นๆ สามารถเข้ามาอ่านและเข้าใจโค้ดของคุณได้รวดเร็วขึ้น การฝึกฝนสร้างและจัดการ Promise ในสถานการณ์ต่างๆ จะช่วยยกระดับทักษะการเขียน JavaScript ของคุณให้เป็นมืออาชีพยิ่งขึ้น

Leave a Reply

Your email address will not be published. Required fields are marked *