新popstate事件触发时,如何终止前一次监听器的执行?
解决连续导航的并行执行问题
要解决连续点击导致的并行导航副作用,核心思路是终止上一次未完成的导航任务,可以通过AbortController中断异步请求,配合状态标记来控制流程,具体实现如下:
1. 新增全局状态控制变量
用来追踪当前是否在导航中,以及存储中断控制器:
let currentNavigationController = null; let isNavigating = false;
2. 修改popstate监听器,加入中断逻辑
每次触发导航时,先终止上一次未完成的任务,再执行新的导航流程:
window.addEventListener("popstate", async () => { // 终止上一次正在进行的导航 if (isNavigating) { currentNavigationController?.abort(); isNavigating = false; } const body = document.body; const currentMain = document.querySelector("main"); isNavigating = true; currentNavigationController = new AbortController(); try { // 显示加载动画 const spinnerMain = await createMain(spinner, currentNavigationController.signal); body.replaceChild(spinnerMain, currentMain); const route = window.location.hash.replace(/#/g, ""); const page = pages[route]; if (!page) { isNavigating = false; return; } // 创建目标页面并初始化 const pageMain = await createMain(page, currentNavigationController.signal); const initiatedPage = await page.init(pageMain); // 确保当前导航未被中断,再更新DOM if (isNavigating) { body.replaceChild(initiatedPage, document.querySelector("main")); } } catch (err) { // 忽略中断错误,只处理其他异常 if (err.name !== "AbortError") { console.error("导航失败:", err); // 可添加错误页面展示逻辑 } } finally { // 仅当当前任务未被中断时,重置状态 if (!currentNavigationController.signal.aborted) { isNavigating = false; currentNavigationController = null; } } });
3. 改造createMain函数,支持请求中断
把中断信号传给fetch,让上一次的模板请求能被主动终止:
async function createMain(page, signal) { const template = await fetch(page.templateUrl, { signal }); const main = document.createElement("main"); main.classList.add(...page.classes); main.innerHTML = await template.text(); return main; }
额外注意点
- 如果你的页面初始化(
page.init)里也有异步操作(比如请求数据),最好也把signal传进去,让这些异步操作也能被中断,彻底终止上一次任务的所有流程。 - 处理页面跳转的链接点击事件时,要确保每次点击都正确触发
pushState和popstate事件,保证中断逻辑能生效。
内容的提问来源于stack exchange,提问作者Ero Stefano
相关产品推荐
相关产品推荐

