# 用实时更新取代轮询

> 别再每隔几秒就问一次 API：每个变化只发一次，减少请求与服务器负载，变化一发生就显示。

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

很多应用是靠「问」来得知变化：每个打开的页面每隔几秒就调用一次你的 API，看看订单、工单或报表有没有变。几乎每次的答案都是「没有新的」，但每次都要花一个请求、一次数据库查询，还有用户手机的电量；真的有变化时，也还要等上一个间隔才看得到。用 Hibira，你的服务器在变化发生时只发一次，所有显示这条数据的页面在几毫秒内就更新。

## 用户得到什么

- 变化一发生就出现在屏幕上，不必等最多五秒。
- 每个标签页都保持最新，不用刷新。
- 手机不再每隔几秒白白唤醒网络。

## 轮询的代价

1,000 人开着页面、每 5 秒轮询一次，一小时就是 720,000 个请求。如果这一小时数据只变了 200 次，其中 99.97% 的请求都没有新内容。改用推送，同一个小时只要 200 条消息，你的 API 只需要处理每个页面第一次加载。

## 用到的功能

- **私有频道**（`private-order-42`），页面上显示的每一样东西一个频道，由你的服务器授权，用户只会收到自己的数据。
- 官方服务端 SDK 的 **`trigger`**，在你保存变更后立刻调用。
- **`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/zh-cn/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：保存时发出变化

数据在哪里改变（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/zh-cn/use-cases/live-dashboards)。
- 经常变动的列表可以一个列表一个频道（`private-orders-team-7`），不必每一行一个。
- 事件大小取决于方案，请看[价格](https://hibira.io/zh-cn/pricing)。
- 想在发布前知道有多少人在看？请看[实时在线人数](https://hibira.io/zh-cn/use-cases/online-user-counts)。
