第 12 章:Owner、并发请求与 AllNetworks
1. scopeKey 如何构造
buildNativeHomePortfolioScopeKey 把影响展示作用域的因素串起来:
return [
enabled ? 'enabled' : 'disabled',
walletId ?? '',
accountId ?? '',
networkId ?? '',
isAllNetworks ? 'all' : 'single',
].join('::');如果这些值中任意一个改变,旧的异步 token 请求、快照 partition、slot owner 和 Home semantic state 都不应继续被视为同一个展示上下文。
2. epoch + generation:两个单调量
| 量 | 保护什么 |
|---|---|
owner epoch | 识别 scope 改变;所有旧 request token 都不再 current |
request generation | 同一 owner 下保证后发/更新的 request 不被更旧结果覆盖 |
sessionId | native slot/intent 的生命周期归属 |
| command revision | 当前 UI 是否仍支持某个 command |
isNativeHomePortfolioRequestCurrent 要求 current owner 的 epoch/scope 与 request 一致,并且 request generation 不低于当前 generation。
3. Single-flight runner
Home portfolio 可能同时收到初始化、刷新、polling 或 Store 重算事件。createNativeHomeSingleFlightRunner 以 epoch::scopeKey 为 key:
- 同 owner 已有 task 时,不开第二个并发请求;
- 把最新 task 替换进去,设置
rerunRequested; - 当前 task 完成后,如果 owner 仍然 current,再执行一次 coalesced task;
- owner 已过期就丢弃 queued rerun。
这比简单 debounce 更适合网络请求:它不丢掉“需要再跑一次”的语义,也不允许陈旧 owner 在 completion 后继续发起工作。
4. AllNetworks 的 authoritative 判定
AllNetworks 会并行请求多个网络,因此“部分成功”不一定能组成 authoritative view。isNativeHomeAllNetworkResponseAuthoritative 把 isSameAllNetworksAccountData !== false 视为可接受;最终 status 还要满足:
- start 成功;
- 非空账户时 expected request count > 0;
- attempt/failure/success count 与 expected count 完全一致;
- 空账户仅在 expected request count 为 0 且 start 成功时才是 success。
5. AllNetworks portfolio projection
buildNativeHomeAllNetworkPortfolioProjection 不直接 concat 数组,而是:
- 为每个 response 的 token 标记
mergeAssets; - 通过
buildSelectorTokenListFromResponses生成 regular/high value projection; - 按 network 分组构建 risk projection;
- 使用 canonical
$key去重; - 重新切出
tokens、smallBalanceTokens、riskTokens及对应 map; - 按 fiat value 排序。
测试特别验证了 derive/aggregate、risk merge、row-level merge marker 和 canonical identity,说明这里的“去重”不是简单按 symbol 或 address。
6. Owner 与 slots 的闭环
同一个 owner 概念跨越四层:
portfolio scopeKey
-> Home session ownerToken
-> IHomeContainerState.owner
-> IHomeContainerSlotBundle.owner
-> native ownsSlot / ownsSlotWithScopeKey如果只在 JS Store 里做 owner check,而不把 owner 传到 native slot,旧 React slot 仍可能覆盖新 native host;如果只在 native 做 check,而不在 Store 做 request/revision check,旧 intent 仍可能触发新业务。两边必须同时做。
7. 这套并发模型的边界
它能防止“旧 owner 结果覆盖新 owner”,但不会让网络永远成功,也不会把 partial AllNetworks response 变成完整数据。失败仍然会进入 error/empty/loading semantic state,由 ViewModel 决定安全的视觉 fallback。