← 使用情境

用即時更新取代輪詢

別再每隔幾秒就問一次 API:每個變化只送一次,減少請求與伺服器負載,變化一發生就顯示。

很多應用是靠「問」來得知變化:每個打開的頁面每隔幾秒就呼叫一次你的 API,看看訂單、工單或報表有沒有變。幾乎每次的答案都是「沒有新的」,但每次都要花一個請求、一次資料庫查詢,還有使用者手機的電量;真的有變化時,也還要等上一個間隔才看得到。用 Hibira,你的伺服器在變化發生時只送一次,所有顯示這筆資料的頁面在幾毫秒內就更新。

使用者得到什麼

  • 變化一發生就出現在畫面上,不必等最多五秒。
  • 每個分頁都保持最新,不用重新整理。
  • 手機不再每隔幾秒白白喚醒網路。

輪詢的代價

1,000 人開著頁面、每 5 秒輪詢一次,一小時就是 720,000 個請求。如果這一小時資料只變了 200 次,其中 99.97% 的請求都沒有新內容。改用推送,同一個小時只要 200 則訊息,你的 API 只需要處理每個頁面第一次載入。

用到的功能

  • 私人頻道(private-order-42),頁面上顯示的每一樣東西一個頻道,由你的伺服器授權,使用者只會收到自己的資料。
  • 官方伺服器 SDK 的 trigger,在你儲存變更後立刻呼叫。
  • pusher:subscription_succeeded,第一次連線和每次重新連線後都會觸發:正是載入目前狀態的時機。

輪詢

每個打開的頁面每 5 秒
GET /api/orders/42
你的 API大多是「沒有新的」

推送

你的 API儲存變更
trigger() 一次
Hibira
WebSocket
每個顯示它的頁面立刻更新
輪詢一問再問;推送每次變化只送一次

步驟 1:找到輪詢迴圈

通常長這樣:

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

步驟 2:載入一次,然後監聽

照 JavaScript 指南 連線,並為私人頻道準備頻道授權端點。訂閱就緒時載入目前狀態,之後每次變化到達就套用:

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:儲存時送出變化

資料在哪裡改變(API 路由、worker、後台操作、webhook),就在寫入成功後發布:

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() 已經會補上斷線期間的空缺。瀏覽器也可能凍結背景分頁,所以分頁回到前景時再載入一次:

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

小提醒

  • 送出頁面重繪需要的內容;資料很大時只送 ID 和版本號,讓頁面自己抓其餘部分。
  • 同一筆資料每秒變很多次?把它們合併,做法請看即時儀表板。
  • 經常變動的清單可以一個清單一個頻道(private-orders-team-7),不必每一列一個。
  • 事件大小依方案而定,請看價格。
  • 想在發布前知道有多少人在看?請看即時在線人數。