Now.js Framework Documentation
TaskMonitorComponent
TaskMonitorComponent
ภาพรวม
TaskMonitorComponent เฝ้างานที่เซิร์ฟเวอร์รันอยู่เบื้องหลัง แล้วรายงานเมื่องานจบ
มันเกิดมาเพื่องานที่ อายุยาวกว่าคำขอที่สั่งมัน — งานฝั่งเซิร์ฟเวอร์ที่วัดกันเป็นนาที
เช่น บีบอัดไฟล์ นำเข้าข้อมูล สร้างรายงาน หรือออกใบรับรอง ทำให้เสร็จภายในคำขอที่สั่ง
ไม่ได้ เซิร์ฟเวอร์จึงเริ่มงานไว้เบื้องหลังแล้วตอบ 202 Accepted กลับมาทันที
จากจุดนั้นเป็นต้นไป หน้าที่กดปุ่มไม่ใช่หน้าที่เห็นผล — แท็บถูกปิด อีกคนเปิดหน้าเดียวกัน
หรือเบราว์เซอร์ถูกทิ้งไว้เป็นชั่วโมง · คำตอบจึงอยู่ในแท็บเบราว์เซอร์ไม่ได้ คอมโพเนนต์นี้
ไปถามเซิร์ฟเวอร์เป็นระยะแทน แล้วแสดงสิ่งที่เซิร์ฟเวอร์ตอบ
ใช้เมื่อไร:
- มีปุ่มที่สั่งงานแล้วเซิร์ฟเวอร์ไปทำต่อทีหลัง
- หน้าจอต้องบอกว่า "ยังทำอยู่" ได้แม้โหลดหน้าใหม่ หรือเปิดจากคนอื่น
- มีอะไรบนหน้าที่ต้องตอบสนองเมื่องานจบ
ทำไมต้องใช้:
- ✅ ไม่ฝังรูปร่าง API ไว้เลย — ทุกฟิลด์แมปผ่าน data attribute
- ✅ ถามถี่เฉพาะตอนมีงานทำ แล้วผ่อนเป็นจังหวะว่างเมื่อไม่มี
- ✅ หยุดถามสนิทเมื่อแท็บถูกซ่อน
- ✅ ถอยจังหวะเมื่อเกิดข้อผิดพลาด แทนที่จะกระหน่ำเซิร์ฟเวอร์ที่กำลังมีปัญหา
- ✅ ยิง DOM event เมื่อแต่ละงานจบ อะไรบนหน้าก็ตอบสนองได้
- ✅ escape ทุกค่าที่เรนเดอร์
การใช้งานพื้นฐาน
แบบ HTML
<div data-component="task-monitor"
data-endpoint="/api/v2/jobs"
data-items="data.active"
data-label-field="label"
data-status-field="status"></div>มาร์กอัปข้างบนคาดหวังคำตอบหน้าตาแบบนี้:
{
"ok": true,
"data": {
"active": [
{"id": 12, "label": "สำรอง example.com", "status": "running", "message": ""}
]
}
}รูปร่างนั้นไม่ได้บังคับ · data-items คือ dot path ไปยังที่ที่อาร์เรย์อยู่ (ใส่ ''
แปลว่าตัว body เป็นอาร์เรย์เอง) ส่วน *-field ทั้งสี่บอกชื่อคีย์ภายในงานหนึ่งชิ้น
แบบ JavaScript
const monitor = TaskMonitorComponent.create(document.querySelector('#tasks'), {
endpoint: '/api/tasks',
interval: 2000,
onFinish: (task) => console.log('เสร็จแล้ว:', task.label)
});
// หลังสั่งงานใหม่ ให้ถามทันทีโดยไม่ต้องรอครบจังหวะ
TaskMonitorComponent.refresh(monitor);ตัวเลือก
ทุกตัวเลือกตั้งเป็น data-* ได้ โดยเขียนแบบ kebab-case: idleInterval คือ
data-idle-interval
| ตัวเลือก | ค่าเริ่มต้น | ความหมาย |
|---|---|---|
endpoint |
'' |
จำเป็น · URL ที่จะไปถาม |
items |
'data.active' |
dot path ไปยังอาร์เรย์ของงาน · '' = ตัว body เอง |
idField |
'id' |
คีย์ที่ใช้ระบุว่างานชิ้นเดียวกันข้ามรอบการถาม |
labelField |
'label' |
คีย์ของชื่อที่จะแสดง |
statusField |
'status' |
คีย์ของสถานะ |
messageField |
'message' |
คีย์ของข้อความผลลัพธ์ |
progressField |
'progress' |
คีย์ของค่า 0–100 · ไม่มี = ไม่แสดงแถบความคืบหน้า |
runningValues |
'queued,running,pending,active' |
ค่าสถานะที่แปลว่า "ยังทำอยู่" |
failedValues |
'failed,error,lost,cancelled' |
ค่าสถานะที่แปลว่าจบแบบไม่สำเร็จ |
interval |
3000 |
จังหวะถามขณะมีงานทำ (มิลลิวินาที) |
idleInterval |
30000 |
จังหวะถามขณะไม่มีงาน · 0 = หยุดถาม |
maxErrors |
0 |
ล้มติดกันกี่ครั้งแล้วเลิก · 0 = ไม่เลิก |
maxInterval |
120000 |
เพดานของการถอยจังหวะเมื่อผิดพลาด |
autoRender |
true |
false = ให้ callback เรนเดอร์เอง |
emptyText |
'' |
ข้อความตอนไม่มีงาน · ว่าง = ไม่เรนเดอร์อะไร |
hideWhenIdle |
true |
ใส่ hidden ให้ element ขณะไม่มีงาน |
notify |
true |
แสดง toast เมื่อแต่ละงานจบ ถ้ามี NotificationManager |
Callback
| Callback | รูปแบบ | เรียกเมื่อ |
|---|---|---|
onInit |
(instance) |
หลังถามสำเร็จครั้งแรก |
onUpdate |
(tasks, instance) |
ทุกครั้งที่ถามสำเร็จ |
onFinish |
(task, instance) |
ครั้งเดียวต่องาน เมื่องานนั้นหยุดทำงาน |
onError |
(error, instance) |
เมื่อถามไม่สำเร็จ |
onRender |
(tasks, instance) |
แทนการเรนเดอร์ในตัว |
onDestroy |
(instance) |
เมื่ออินสแตนซ์ถูกทำลาย |
เมธอด
| เมธอด | คำอธิบาย |
|---|---|
init(options?) |
ติดตั้งให้ทุก element ที่ตรง · ถูกเรียกเองเมื่อ DOM พร้อม |
create(element, options?) |
สร้างตัวเฝ้าหนึ่งตัว · คืนอินสแตนซ์ หรือ null |
refresh(instanceOrElement) |
ถามเดี๋ยวนี้ ไม่สนใจตารางเวลา |
destroy(instanceOrElement) |
หยุดถามและลืมอินสแตนซ์นั้น |
เหตุการณ์
task-monitor:finished
ยิงครั้งเดียวต่องาน เมื่องานนั้นหยุดทำงาน · เป็น bubbling event ผู้ฟังที่ระดับไหน
เหนือขึ้นไปก็รับได้
document.addEventListener('task-monitor:finished', (event) => {
const {task, label, message, failed} = event.detail;
if (!failed) TableManager.refresh('backups');
});เป็น DOM event ไม่ใช่แค่ callback เพราะหน้าเว็บต้องตอบสนองได้โดยไม่ต้องถืออินสแตนซ์
ไว้ — จะรีเฟรชตาราง ปิดกล่องโต้ตอบ หรือเปิดปุ่มที่อยู่คนละมุมของหน้าจอก็ได้
ตัดสินว่า "จบแล้ว" อย่างไร
คอมโพเนนต์จำ id ของงานที่กำลังทำอยู่ในรอบก่อนหน้า · งานที่เคยอยู่ในชุดนั้นแล้วตอนนี้
ไม่ได้ทำงานแล้ว = จบ ไม่ว่าเซิร์ฟเวอร์จะรายงานว่าจบ หรือเอามันออกจากรายการไปเลย ·
สองอย่างนั้นมีความหมายเดียวกันสำหรับคนที่กำลังรอดูอยู่ และถ้าไม่จำไว้ งานที่หายไปเฉย ๆ
จะแยกไม่ออกจากงานที่ไม่เคยมีอยู่
runningValues เป็นชุดปิดโดยตั้งใจ · สถานะที่ไม่มีใครคาดไว้ — เซิร์ฟเวอร์รุ่นใหม่เพิ่ม
cancelled มา หรือพิมพ์ผิด — จะถูกนับว่าจบและหยุดถาม ดีกว่าปล่อยให้หน้าเว็บถามไป
เรื่อย ๆ เพื่อสิ่งที่จบไปแล้ว
พฤติกรรมการถาม
สามอย่างที่กันไม่ให้กลายเป็นภาระของเซิร์ฟเวอร์:
- ผ่อนเมื่อว่าง — ไม่มีงานทำแล้วจังหวะจะลดลงเป็น
idleIntervalหรือหยุดสนิทที่0 - แท็บที่ถูกซ่อนหยุดสนิท — แท็บพื้นหลังไม่ถามเลยสักครั้ง และกลับมาดูเมื่อไรก็ถามทันที
หน้าจอจึงไม่เคยเก่ากว่าเวลาที่ใช้ตอบหนึ่งคำขอ - ถอยจังหวะเมื่อพลาด — ล้มติดกันแต่ละครั้งเพิ่มเวลารอเป็นสองเท่าจนถึง
maxInterval·
ถ้าไม่มีข้อนี้ ตัวเฝ้าที่เปิดค้างไว้ตอนเซิร์ฟเวอร์กำลังรีสตาร์ตจะยิงคำขอทุกไม่กี่วินาที
ตราบเท่าที่แท็บยังเปิดอยู่ ซึ่งคือรูปร่างของการ DoS ตัวเองใส่เครื่องที่กำลังมีปัญหาอยู่แล้ว
การจัดสไตล์
คอมโพเนนต์เรนเดอร์คลาสของตัวเอง และมาพร้อม Now/css/task-monitor.css:
<ul class="task-monitor-list">
<li class="task-monitor-item is-running">
<span class="task-monitor-status">running</span>
<span class="task-monitor-label">สำรอง example.com</span>
<progress class="task-monitor-progress" max="100" value="40"></progress>
<span class="task-monitor-message">…</span>
</li>
</ul>สถานะคือ is-running / is-done / is-failed — เป็น modifier ของคอมโพเนนต์เอง
ไม่ยืมคลาสจากสไตล์ของแอปพลิเคชันใด · คอมโพเนนต์ของเฟรมเวิร์กที่เรนเดอร์ pill-danger
จะดูถูกต้องเฉพาะในโปรเจกต์เดียวที่บังเอิญนิยามคลาสนั้นไว้
ถ้าต้องการหน้าตาคนละแบบไปเลย ใช้ onRender แล้วไม่ต้องสนใจทั้งหมดนี้
ความปลอดภัย
ทุกอย่างที่เรนเดอร์มาจากเซิร์ฟเวอร์ และชื่องานมักเป็นชื่อไฟล์หรือชื่อโดเมน ที่คนอื่นตั้ง —
ซึ่งเป็นค่าที่ลูกค้าควบคุมได้พอดี · ทุกสตริงถูก escape ก่อนถึง innerHTML เพราะถ้าเรนเดอร์
ดิบ ๆ ค่าใดค่าหนึ่งในนั้นคือ stored XSS บนหน้าจอของผู้ดูแลเอง
onRender แทนการเรนเดอร์ในตัวทั้งหมด ผู้เขียนตัวเรนเดอร์เองจึงต้องรับผิดชอบการ escape เอง
สิ่งที่พึ่งพา
| สิ่งที่พึ่งพา | จำเป็นไหม | ใช้ทำอะไร |
|---|---|---|
CoreObserver |
ไม่ | ติดตั้งให้ element ที่เพิ่มมาทีหลัง · ถ้าไม่มีจะใช้ MutationObserver ของตัวเอง |
window.http |
ไม่ | ยิงคำขอพร้อม session และ CSRF ที่แอปใช้อยู่ · ถ้าไม่มีจะใช้ fetch |
NotificationManager |
ไม่ | toast ที่แสดงเมื่องานจบ |
การทดสอบ
tests/task-monitor.mjs รันบันเดิลจริงในเบราว์เซอร์จริงกับเซิร์ฟเวอร์ HTTP จริง —
18 ข้อ ครอบคลุมการติดตั้ง เหตุการณ์ตอนจบ สถานะที่ไม่รู้จัก การ escape การถอยจังหวะ
เมื่อผิดพลาด และการเก็บกวาดตัวจับเวลา
cd tests && node task-monitor.mjs