ทำไมต้องมี 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>