ทำไมต้องมี Wrapper?
เวลาพัฒนา Web App ด้วย Google Apps Script (GAS) เรามักต้องเขียนโค้ดฝั่ง Client (HTML/JS) ให้เรียกฟังก์ชันฝั่ง Server (Code.gs) ผ่าน google.script.run
โดยปกติ การเขียน google.script.run
ตรง ๆ จะมีลักษณะเช่นนี้:
แม้จะใช้งานได้ แต่ปัญหาคือ
- โค้ดซ้ำ ๆ เยอะ (ต้องเขียน Success/Failure handler ทุกครั้ง)
- ไม่รองรับ
async/await
โดยตรง (ทำให้โค้ดอ่านยากเมื่อมีหลายการเรียก) - ทำให้การจัดการ Error ไม่เป็นระบบ
👉 ตรงนี้เองที่ GoogleRun Wrapper ถูกสร้างขึ้นมา เพื่อเปลี่ยน google.script.run
ให้อยู่ในรูปที่ใช้ง่ายขึ้น เหมือนเรียกใช้ API ปกติ
ตัวอย่าง GoogleRun Wrapper
การใช้งาน:
✅ ข้อดี (Pros)
1. ใช้ง่ายขึ้นเรียกเหมือนฟังก์ชันปกติ
await googleRun("func", arg1, arg2)
2. รองรับ async/await
ทำให้โค้ดอ่านง่าย ไม่ซับซ้อน
3. จัดการ Error ได้เป็นระบบ
ไม่ต้องเขียน
withFailureHandler
ทุกครั้ง4. ลดการเขียนโค้ดซ้ำ
Handler ส่วนกลางอยู่ใน Wrapper เดียว
5. ใช้ซ้ำได้หลายโปรเจกต์
สามารถ copy ไปใช้ใน web app อื่น ๆ ได้เลย
❌ ข้อเสีย (Cons)
1. ต้องเขียน Wrapper เพิ่มเองผู้เริ่มต้นอาจไม่เข้าใจว่าเกิดอะไรขึ้นเบื้องหลัง
2. Debug ยากขึ้นเล็กน้อย
ถ้า Wrapper มีบั๊ก อาจไม่รู้ว่าปัญหาอยู่ที่ฝั่งไหน (Client/Server)
3. ไม่เหมาะกับโค้ดเล็กมาก ๆ
ถ้าโปรเจกต์มีแค่การเรียก
google.script.run
1–2 ครั้ง อาจไม่จำเป็น🔹 ประโยชน์ (Use Cases)
1. Web App ที่ต้องติดต่อกับ Google Sheets/Drive หลายครั้งเช่น ระบบจองห้อง, ระบบเช็กชื่อ, ระบบบันทึกข้อมูล
2. โปรเจกต์ที่ต้องการโค้ดอ่านง่าย
ใช้
async/await
แทนการเขียน callback ซ้อนกัน3. โปรเจกต์ที่ทีมพัฒนาหลายคน
Wrapper ทำให้ทุกคนใช้โค้ดรูปแบบเดียวกัน ลดความผิดพลาด
สรุป
GoogleRun Wrapper เป็นวิธีทำให้ google.script.run
ใช้งานสะดวกขึ้นมาก โดยเปลี่ยนการทำงานแบบ Callback ให้เป็น Promise/async-await
- ถ้าโปรเจกต์เล็ก: เขียนตรง ๆ ก็พอ
- ถ้าโปรเจกต์ใหญ่: ใช้ Wrapper จะช่วยให้อ่านง่าย ดูแลรักษาง่าย และลดความซ้ำซ้อน
ตัวอย่างการใช้งาน
Code.gs
/** * ชี้ไปที่ Spreadsheet */ const SPREADSHEET_ID = "YOUR_SPREADSHEET_ID"; const SHEET_NAME = "Users"; /** * โหลดไฟล์ HTML (Frontend) */ function doGet() { return HtmlService.createTemplateFromFile("index").evaluate() .setTitle("Async/Await Demo") .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); } /** * โหลดข้อมูลจาก Google Sheet */ function getUsers() { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); const values = sheet.getDataRange().getValues(); // แปลงเป็น JSON const headers = values.shift(); return values.map(row => { let obj = {}; headers.forEach((h, i) => obj[h] = row[i]); return obj; }); } /** * เพิ่มผู้ใช้ใหม่ */ function addUser(user) { const ss = SpreadsheetApp.openById(SPREADSHEET_ID); const sheet = ss.getSheetByName(SHEET_NAME); sheet.appendRow([user.name, user.email, user.role]); return { success: true, message: "เพิ่มผู้ใช้สำเร็จ ✅" }; }
index.html
<!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <title>Async/Await Demo</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body class="p-4"> <div class="container"> <h2 class="mb-4">📋 รายชื่อผู้ใช้งาน</h2> <!-- ตารางผู้ใช้ --> <table class="table table-bordered"> <thead> <tr> <th>ชื่อ</th> <th>อีเมล</th> <th>บทบาท</th> </tr> </thead> <tbody id="userTable"></tbody> </table> <!-- ฟอร์มเพิ่มผู้ใช้ --> <h4 class="mt-4">➕ เพิ่มผู้ใช้ใหม่</h4> <form id="userForm"> <div class="mb-3"> <label class="form-label">ชื่อ</label> <input type="text" class="form-control" id="name" required> </div> <div class="mb-3"> <label class="form-label">อีเมล</label> <input type="email" class="form-control" id="email" required> </div> <div class="mb-3"> <label class="form-label">บทบาท</label> <select class="form-select" id="role" required> <option value="Admin">Admin</option> <option value="User">User</option> </select> </div> <button type="submit" class="btn btn-primary">บันทึก</button> </form> <div id="message" class="mt-3"></div> </div> <script> // ✅ async/await wrapper for google.script.run function runAsync(funcName, ...args) { return new Promise((resolve, reject) => { google.script.run .withSuccessHandler(resolve) .withFailureHandler(reject)[funcName](...args); }); } // โหลดข้อมูลมาใส่ตาราง async function loadUsers() { try { const users = await runAsync("getUsers"); const tbody = document.getElementById("userTable"); tbody.innerHTML = ""; users.forEach(u => { const tr = document.createElement("tr"); tr.innerHTML = `<td>${u.name}</td><td>${u.email}</td><td>${u.role}</td>`; tbody.appendChild(tr); }); } catch (err) { console.error("โหลดข้อมูลล้มเหลว", err); } } // จัดการ submit form document.getElementById("userForm").addEventListener("submit", async (e) => { e.preventDefault(); const user = { name: document.getElementById("name").value, email: document.getElementById("email").value, role: document.getElementById("role").value }; try { const res = await runAsync("addUser", user); document.getElementById("message").innerHTML = `<div class="alert alert-success">${res.message}</div>`; e.target.reset(); loadUsers(); // reload table } catch (err) { document.getElementById("message").innerHTML = `<div class="alert alert-danger">❌ เกิดข้อผิดพลาด: ${err}</div>`; } }); // เริ่มโหลดเมื่อเปิดหน้า loadUsers(); </script> </body> </html>