# เลิกโพลลิงแล้วใช้การอัปเดตแบบเรียลไทม์

> เลิกถาม API ทุกไม่กี่วินาที ส่งการเปลี่ยนแปลงแต่ละครั้งเพียงครั้งเดียว ลดคำขอและภาระเซิร์ฟเวอร์ และแสดงอัปเดตทันทีที่เกิดขึ้น

Source: https://hibira.io/th/use-cases/replace-polling

หลายแอปรู้ว่ามีอะไรเปลี่ยนด้วยการ "ถาม": ทุกหน้าที่เปิดอยู่จะเรียก API ทุกไม่กี่วินาทีเพื่อดูว่าคำสั่งซื้อ ทิกเก็ต หรือรายงานเปลี่ยนหรือยัง คำตอบแทบทุกครั้งคือ "ไม่มีอะไรใหม่" แต่ทุกครั้งต้องเสียคำขอหนึ่งครั้ง คิวรีฐานข้อมูลหนึ่งครั้ง และแบตเตอรี่มือถือของผู้ใช้ ส่วนการเปลี่ยนแปลงจริงก็ยังต้องรอถึงรอบถัดไปกว่าจะเห็น เมื่อใช้ Hibira เซิร์ฟเวอร์จะส่งการเปลี่ยนแปลงเพียงครั้งเดียวตอนที่มันเกิดขึ้น และทุกหน้าที่แสดงข้อมูลนั้นจะอัปเดตภายในไม่กี่มิลลิวินาที

## ผู้ใช้ของคุณได้อะไร

- เห็นการเปลี่ยนแปลงทันทีที่เกิดขึ้น ไม่ต้องรอสูงสุดห้าวินาที
- ทุกแท็บเป็นข้อมูลล่าสุดเสมอโดยไม่ต้องรีโหลด
- มือถือไม่ต้องปลุกสัญญาณเครือข่ายทุกไม่กี่วินาทีโดยเปล่าประโยชน์

## ต้นทุนของการโพล

ผู้ใช้ 1,000 คนเปิดหน้าไว้และโพลทุก 5 วินาที เท่ากับ 720,000 คำขอต่อชั่วโมง ถ้าในชั่วโมงนั้นข้อมูลเปลี่ยนเพียง 200 ครั้ง คำขอ 99.97% จะไม่มีอะไรใหม่เลย เมื่อเปลี่ยนเป็นการพุช ชั่วโมงเดียวกันใช้เพียง 200 ข้อความ และ API ของคุณรับแค่การโหลดครั้งแรกของแต่ละหน้า

## สร้างด้วย

- **ช่องส่วนตัว** (`private-order-42`) หนึ่งช่องต่อสิ่งที่หน้าเว็บแสดง เซิร์ฟเวอร์ของคุณเป็นผู้อนุญาต ผู้ใช้จึงได้รับเฉพาะข้อมูลของตัวเอง
- **`trigger`** ใน SDK ฝั่งเซิร์ฟเวอร์อย่างเป็นทางการ เรียกทันทีหลังบันทึกการเปลี่ยนแปลง
- **`pusher:subscription_succeeded`** ซึ่งเกิดขึ้นตอนเชื่อมต่อครั้งแรกและทุกครั้งหลังเชื่อมต่อใหม่ จึงเป็นจังหวะที่เหมาะกับการโหลดสถานะปัจจุบัน

```text
flow: การโพลถามซ้ำแล้วซ้ำอีก การพุชส่งแต่ละการเปลี่ยนแปลงครั้งเดียว
== การโพล
ทุกหน้าที่เปิดอยู่|ทุก 5 วินาที --GET /api/orders/42--> API ของคุณ|ส่วนใหญ่ "ไม่มีอะไรใหม่"
== การพุช
API ของคุณ|บันทึกการเปลี่ยนแปลง --`trigger()` ครั้งเดียว--> *Hibira --WebSocket--> ทุกหน้าที่แสดงข้อมูลนี้|อัปเดตทันที
```

## ขั้นตอนที่ 1: หาลูปที่โพลอยู่

ปกติจะหน้าตาแบบนี้:

```js
setInterval(async () => {
  render(await fetch('/api/orders/42').then((r) => r.json()));
}, 5000);
```

## ขั้นตอนที่ 2: โหลดครั้งเดียว แล้วรอฟัง

เชื่อมต่อตาม[คู่มือ JavaScript](https://hibira.io/th/guides/javascript) พร้อมปลายทางอนุญาตช่องสำหรับช่องส่วนตัว โหลดสถานะปัจจุบันเมื่อการสมัครพร้อม แล้วนำการเปลี่ยนแปลงแต่ละครั้งไปใช้เมื่อมาถึง:

```js
const load = async () => render(await fetch('/api/orders/42').then((r) => r.json()));

const channel = pusher.subscribe('private-order-42');
channel.bind('pusher:subscription_succeeded', load);
channel.bind('updated', (order) => render(order));
```

## ขั้นตอนที่ 3: ส่งการเปลี่ยนแปลงตอนบันทึก

ไม่ว่าข้อมูลจะเปลี่ยนที่ไหน (route ของ API, worker, การกระทำของแอดมิน, webhook) ให้ส่งหลังเขียนสำเร็จ:

```js
app.patch('/api/orders/:id', requireLogin, async (req, res) => {
  const order = await db.orders.update(req.params.id, req.body);
  await pusher.trigger(`private-order-${order.id}`, 'updated', order);
  res.json(order);
});
```

## ขั้นตอนที่ 4: มีตาข่ายนิรภัยเบา ๆ

เพราะการสมัครจะเกิดขึ้นอีกครั้งหลังเชื่อมต่อใหม่ `load()` จึงเติมช่องว่างช่วงที่หลุดการเชื่อมต่อให้แล้ว เบราว์เซอร์ยังอาจหยุดแท็บที่อยู่เบื้องหลัง จึงควรโหลดอีกครั้งเมื่อแท็บกลับมาแสดง:

```js
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') load();
});
```

## เคล็ดลับ

- ส่งเฉพาะสิ่งที่หน้าเว็บต้องใช้วาดใหม่ ถ้าข้อมูลใหญ่ให้ส่งแค่ ID กับเวอร์ชัน แล้วให้หน้าเว็บดึงส่วนที่เหลือเอง
- ข้อมูลเดียวกันเปลี่ยนหลายครั้งต่อวินาที? รวมเป็นชุดแบบใน[แดชบอร์ดเรียลไทม์](https://hibira.io/th/use-cases/live-dashboards)
- รายการที่เปลี่ยนบ่อยใช้หนึ่งช่องต่อรายการ (`private-orders-team-7`) แทนหนึ่งช่องต่อแถวได้
- ขนาดของอีเวนต์ขึ้นอยู่กับแพ็กเกจ ดูที่[ราคา](https://hibira.io/th/pricing)
- อยากรู้ว่ามีคนดูอยู่กี่คนก่อนส่ง? ดูที่[จำนวนผู้ใช้ออนไลน์](https://hibira.io/th/use-cases/online-user-counts)
