关键在于按任务性质匹配执行模型:强依赖串行、无依赖并行、部分依赖可控并发;加载策略选 defer/async/import() 控制时机;重活移至 Web Worker;用优先级队列管理请求。

平衡 JavaScript 异步性能与执行顺序,关键不是“选 async 还是 defer”,也不是“用 await 还是 Promise.all”,而是根据任务性质匹配执行模型:该串行的不强行并发,该并行的不盲目串行,该延后的不提前加载,该保序的不依赖运气。
按依赖关系选执行模型
执行顺序不是目标,而是业务逻辑的自然结果。强行统一串行或并行,反而损害性能和可维护性:
-
有强依赖时必须串行:比如“获取 token → 带 token 请求用户信息 → 根据用户角色加载对应菜单”。这时用
for...of + await最清晰,每个步骤等前一步结果,避免竞态和空值错误 -
无依赖且需快速响应时优先并行:首页同时拉取用户信息、导航栏、未读消息——三者互不依赖,用
Promise.all([fetchUser(), fetchMenu(), fetchUnread()])能显著缩短总耗时 -
部分依赖+资源受限时用可控并发:批量上传 100 个文件,浏览器限制单域名最多 6 个并发连接。可用
p-limit或手动分批(如每批 4 个),既避免阻塞,又防止打爆服务端或内存溢出
用对加载策略,从源头控制顺序与时机
脚本加载阶段就决定了后续执行能否稳定保序:
-
defer 用于有明确先后依赖的业务脚本:比如
lodash.js必须先于app.js执行,二者都加defer,浏览器自动按 HTML 中出现顺序执行,且不阻塞 DOM 解析 -
async 仅用于完全独立的脚本:如统计 SDK、错误监控上报。它一加载完就执行,不保证与其他脚本顺序,也别在里面操作 DOM —— 若必须操作,应监听
DOMContentLoaded -
动态导入(
import())按需触发:路由切换时加载对应模块,按钮点击后才加载 Excel 导出逻辑。既减少首屏体积,又天然隔离执行时机,避免提前竞争
把重活移出主线程,释放顺序调度空间
很多“顺序问题”其实是主线程被占满导致的假性失控:
立即学习“Java免费学习笔记(深入)”;
- 计算密集型任务交给 Web Worker:如解析大 JSON、图像滤镜处理、加密运算。主线程只发指令、收结果,不卡 UI,也不干扰事件循环中其他微/宏任务的节奏
- DOM 批量操作 + 防抖节流:监听滚动时频繁更新列表?合并成一次 DocumentFragment 插入;搜索框输入实时请求?用防抖延迟 300ms 再发,避免发 20 个请求只用最后一个结果
-
微任务慎用,避免“隐式串行”:连续 10 个
Promise.then()看似并行,实则全部挤在同一个微任务队列里顺序执行。若中间某步耗时高(如同步解析大数组),会拖慢后续所有微任务,甚至影响页面渲染
用优先级队列管理异步请求
浏览器并发限制下,请求发起顺序 ≠ 实际执行顺序。靠业务价值排序,才能让关键路径不被拖慢:
- 给请求标记优先级:登录后首页的“用户信息”标 high,“广告位”标 low,“行为埋点”标 background(可失败静默)
- 拦截器实现请求排队:Axios 或自建 fetch 封装中,高优请求插队到队列头部,低优请求延迟 500ms 再发,或等高优完成后再启动
- 超时与降级配合保序体验:某个中优先级接口迟迟不响应?主动 reject 并 fallback 到本地缓存或空数据,不让它阻塞后续高优请求的回调处理



















