这本杂志六个月里反复写着同一句话。
离线与缓存行为 — 没有任何样例试过
(原样摘自回望 2026 年 1 月。)这一篇把那行删掉。
为什么它一直留着
不是懒。离线很难测。 测「有连接」的状态很容易 — 跑一下能用就行。测「没有」的状态就得先把它弄没,而「没有」有好几种。
这个样例只处理其中最糟的两种。
- 没地方可送。 而客人就站在你面前。
- 送了却没回音。 到没到,点单板无从知晓。
这两者导向相反方向的失败。不处理第一个,订单会消失;不处理第二个,会被收两次钱。加一个队列只解决第一个,第二个就冒出来了 — 这就是这一篇想说的。
收银台在的时候

till: connected
online: took 1, delivered 1, till has 1 (3.5k won)
平平无奇。按了一杯咖啡,收银台收下了。
收银台消失
till: process gone — the pad is now on its own
我们杀掉了进程。对餐车来说就是热点断了,而从点单板这一侧看没有区别 — 一样的只是「发出去的东西收不到回应」。
然后又接了三单。

offline: took pad1-002 (sandwich) — nothing was sent
offline: took pad1-003 (coffee) — nothing was sent
offline: took pad1-004 (juice) — nothing was sent
offline: pad still took 3 orders, 3 waiting, ids pad1-002 pad1-003 pad1-004
画面是红的,是为了诚实。 把「东西根本没发出去」藏起来,那不是离线支持,是离线遮掩。可以继续接单,但必须让人看得见还有几单没走。
发件箱就是全部答案
这个样例的答案是一个类。点单板从不直接调用收银台。
/// The order pad never calls the till directly. It writes into here, and here
/// tries to deliver. That one indirection is what lets the person behind the
/// counter keep working while the till is unreachable, and it is also what
/// makes the failure honest: the pad can show how many orders are still
/// undelivered, because the outbox knows.
这一层同时给了两样东西:能继续干活,以及能说出积压了几单。
规则只有两条。
① id 在这里生成,在第一次尝试之前。
final p = Pending('$device-${(++_minted).toString().padLeft(3, '0')}', item);
如果由收银台分配 id,重发就变成了新订单。那客人就被收两次钱。重试要安全,前提是「要重试什么」在重试之前就已定下。
② 按顺序发,第一次失败就停。
} catch (_) {
// Still ours. Leave it at the front and stop — the ones behind it are
// newer and must not overtake.
break;
}
跳过去先发后面的,账本就把这个早上记错了。12 点的订单盖在 11 点的前面,那就是一本错的账。
而且队列在文件里。
/// Where the queue lives between app launches. A queue that only exists in
/// memory is not a queue — closing the lid is the same as losing the orders.
final File spoolFile;