# สร้างแอปธนาคาร ตอนที่ 3: วิธีการดึงและใช้งานข้อมูล ## แบบทดสอบก่อนเรียน [แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/web/quiz/45) ### บทนำ หัวใจสำคัญของทุกแอปพลิเคชันเว็บคือ *ข้อมูล* ข้อมูลอาจมีหลายรูปแบบ แต่จุดประสงค์หลักคือการแสดงข้อมูลให้ผู้ใช้เห็น ด้วยแอปเว็บที่มีความซับซ้อนและโต้ตอบได้มากขึ้น วิธีที่ผู้ใช้เข้าถึงและโต้ตอบกับข้อมูลจึงกลายเป็นส่วนสำคัญของการพัฒนาเว็บ ในบทเรียนนี้ เราจะเรียนรู้วิธีดึงข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัส และใช้ข้อมูลนี้เพื่อแสดงผลบนหน้าเว็บโดยไม่ต้องโหลด HTML ใหม่ ### ความต้องการเบื้องต้น คุณต้องสร้าง [ฟอร์มเข้าสู่ระบบและลงทะเบียน](../2-forms/README.md) ของแอปเว็บในบทเรียนก่อนหน้านี้ นอกจากนี้ คุณต้องติดตั้ง [Node.js](https://nodejs.org) และ [รันเซิร์ฟเวอร์ API](../api/README.md) ในเครื่องของคุณเพื่อให้ได้ข้อมูลบัญชี คุณสามารถทดสอบว่าเซิร์ฟเวอร์ทำงานได้อย่างถูกต้องโดยรันคำสั่งนี้ในเทอร์มินัล: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## AJAX และการดึงข้อมูล เว็บไซต์แบบดั้งเดิมจะอัปเดตเนื้อหาที่แสดงเมื่อผู้ใช้คลิกลิงก์หรือส่งข้อมูลผ่านฟอร์ม โดยการโหลดหน้า HTML ใหม่ทั้งหมด ทุกครั้งที่ต้องโหลดข้อมูลใหม่ เซิร์ฟเวอร์เว็บจะส่งหน้า HTML ใหม่ที่ต้องประมวลผลโดยเบราว์เซอร์ ซึ่งจะขัดจังหวะการกระทำของผู้ใช้ในปัจจุบันและจำกัดการโต้ตอบระหว่างการโหลดกระบวนการนี้เรียกว่า *Multi-Page Application* หรือ *MPA*  เมื่อแอปพลิเคชันเว็บเริ่มมีความซับซ้อนและโต้ตอบได้มากขึ้น เทคนิคใหม่ที่เรียกว่า [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming)) ได้เกิดขึ้น เทคนิคนี้ช่วยให้แอปเว็บสามารถส่งและดึงข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัสโดยใช้ JavaScript โดยไม่ต้องโหลดหน้า HTML ใหม่ ส่งผลให้การอัปเดตเร็วขึ้นและการโต้ตอบของผู้ใช้ราบรื่นขึ้น เมื่อได้รับข้อมูลใหม่จากเซิร์ฟเวอร์ หน้า HTML ปัจจุบันสามารถอัปเดตได้ด้วย JavaScript โดยใช้ [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model) API วิธีนี้ได้พัฒนาเป็นสิ่งที่เรียกว่า [*Single-Page Application* หรือ *SPA*](https://en.wikipedia.org/wiki/Single-page_application)  เมื่อ AJAX ถูกนำมาใช้ครั้งแรก API เดียวที่สามารถดึงข้อมูลแบบอะซิงโครนัสได้คือ [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest) แต่เบราว์เซอร์สมัยใหม่ยังมี [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API) ที่สะดวกและทรงพลังมากกว่า ซึ่งใช้ promises และเหมาะสมกับการจัดการข้อมูล JSON มากกว่า > แม้ว่าเบราว์เซอร์สมัยใหม่ทั้งหมดจะรองรับ `Fetch API` แต่หากคุณต้องการให้แอปเว็บของคุณทำงานบนเบราว์เซอร์รุ่นเก่าหรือเบราว์เซอร์ที่ล้าสมัย ควรตรวจสอบ [ตารางความเข้ากันได้บน caniuse.com](https://caniuse.com/fetch) ก่อนเสมอ ### งาน ใน [บทเรียนก่อนหน้า](../2-forms/README.md) เราได้สร้างฟอร์มลงทะเบียนเพื่อสร้างบัญชี ตอนนี้เราจะเพิ่มโค้ดเพื่อเข้าสู่ระบบด้วยบัญชีที่มีอยู่ และดึงข้อมูลของบัญชี เปิดไฟล์ `app.js` และเพิ่มฟังก์ชัน `login` ใหม่: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; } ``` เริ่มต้นด้วยการดึงองค์ประกอบฟอร์มด้วย `getElementById()` จากนั้นดึงชื่อผู้ใช้จากอินพุตด้วย `loginForm.user.value` ทุกฟอร์มคอนโทรลสามารถเข้าถึงได้โดยใช้ชื่อ (ที่กำหนดใน HTML ด้วยแอตทริบิวต์ `name`) เป็นพร็อพเพอร์ตีของฟอร์ม ในลักษณะเดียวกับที่เราทำสำหรับการลงทะเบียน เราจะสร้างฟังก์ชันอีกตัวเพื่อทำการร้องขอเซิร์ฟเวอร์ แต่ครั้งนี้เพื่อดึงข้อมูลบัญชี: ```js async function getAccount(user) { try { const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user)); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` เราใช้ `fetch` API เพื่อร้องขอข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัส แต่ครั้งนี้เราไม่ต้องการพารามิเตอร์เพิ่มเติมนอกจาก URL ที่จะเรียก เนื่องจากเรากำลังดึงข้อมูลเท่านั้น โดยค่าเริ่มต้น `fetch` จะสร้างคำร้องขอ HTTP [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) ซึ่งเป็นสิ่งที่เราต้องการในที่นี้ ✅ `encodeURIComponent()` เป็นฟังก์ชันที่ใช้เข้ารหัสอักขระพิเศษสำหรับ URL ปัญหาใดที่อาจเกิดขึ้นหากเราไม่เรียกใช้ฟังก์ชันนี้และใช้ค่า `user` โดยตรงใน URL? ตอนนี้มาอัปเดตฟังก์ชัน `login` ของเราเพื่อใช้ `getAccount`: ```js async function login() { const loginForm = document.getElementById('loginForm') const user = loginForm.user.value; const data = await getAccount(user); if (data.error) { return console.log('loginError', data.error); } account = data; navigate('/dashboard'); } ``` เนื่องจาก `getAccount` เป็นฟังก์ชันแบบอะซิงโครนัส เราจึงต้องใช้คำสั่ง `await` เพื่อรอผลลัพธ์จากเซิร์ฟเวอร์ และเช่นเดียวกับการร้องขอเซิร์ฟเวอร์ใดๆ เราต้องจัดการกรณีที่เกิดข้อผิดพลาดด้วย สำหรับตอนนี้เราจะเพิ่มข้อความใน log เพื่อแสดงข้อผิดพลาด และจะกลับมาปรับปรุงในภายหลัง จากนั้นเราต้องเก็บข้อมูลไว้ที่ใดที่หนึ่งเพื่อใช้แสดงข้อมูลในแดชบอร์ดในภายหลัง เนื่องจากตัวแปร `account` ยังไม่มีอยู่ เราจะสร้างตัวแปร global สำหรับมันที่ด้านบนของไฟล์: ```js let account = null; ``` หลังจากบันทึกข้อมูลผู้ใช้ลงในตัวแปรแล้ว เราสามารถนำทางจากหน้า *login* ไปยัง *dashboard* โดยใช้ฟังก์ชัน `navigate()` ที่เรามีอยู่แล้ว สุดท้าย เราต้องเรียกฟังก์ชัน `login` ของเราทุกครั้งที่ฟอร์มเข้าสู่ระบบถูกส่ง โดยการแก้ไข HTML: ```html