async/await阻塞浏览器 jQuery show/hide加载提示失效问题排查
async/await场景下加载提示显隐异常问题解答
问题复现代码
self.handleAction = async function (event) { $("#someDivID").show(); await __method1(); //log something await __method2(); $("#someDivID").hide(); }
问题1:async/await异步执行思路的错误点
你的async/await使用思路存在本质认知偏差,遗漏了JS单线程事件循环的核心规则:
async/await只是异步流程的语法糖,没有能力把同步的CPU密集型计算自动转成异步非阻塞任务。JS主线程同一时间只能执行一个任务,同步计算运行时会完全阻塞DOM渲染、用户交互、异步任务调度等所有流程,直到当前同步任务执行完毕才会让出控制权。await的作用是暂停当前async函数的执行,等待其后的Promise状态落定后再恢复函数执行。如果await后面跟的是纯同步执行的逻辑(比如本次场景中没有任何异步API调用、直接返回结果的纯计算方法__method1/__method2),await不会产生任何让出主线程的效果,计算逻辑会立刻霸占主线程跑完。- 其他场景下同款写法生效,根本原因是那些场景里
await的是fetch、网络请求这类本身就会被浏览器放到异步任务队列的操作,调用后会立刻让出主线程,不是async/await本身的魔法。
问题2:show()/hide()效果不可见的根本原因
浏览器的DOM渲染不是实时触发的:每次修改DOM属性后,浏览器不会立刻把变化重绘到页面上,而是会等当前调用栈里的所有同步任务全部执行完,到了下一个渲染帧周期,才会统一计算样式、完成重排重绘。
你的代码实际执行流完全符合主线程阻塞的特征:
- 调用
show()修改元素的display属性为显示,这个修改只更新了内存中的DOM状态,还没来得及渲染到页面上 - 立刻执行
__method1、__method2的长耗时同步计算,全程霸占主线程,浏览器完全没有机会执行渲染操作 - 两个计算任务全部跑完后,立刻调用
hide()把元素的display属性改回隐藏 - 所有同步逻辑执行完毕,浏览器终于拿到主线程控制权准备渲染,此时读取到的DOM状态已经是元素隐藏,自然全程看不到加载提示出现。
修复方案
在show()调用后主动让出一次主线程,给浏览器留出完成DOM渲染的时间窗口,再执行后续长计算逻辑即可:
self.handleAction = async function (event) { $("#someDivID").show(); // 让出主线程,等待浏览器完成当前帧渲染 await new Promise(resolve => setTimeout(resolve, 0)); await __method1(); // 日志逻辑 await __method2(); $("#someDivID").hide(); }
如果__method1、__method2的计算量极大,哪怕让出一次主线程,后续计算过程还是会阻塞页面交互,可以选择两个优化方向:
- 把大计算任务拆分成多个小任务,分批通过
setTimeout调度执行,每执行完一批就让出一次主线程 - 把纯计算逻辑迁移到Web Worker中执行,完全不占用主线程,从根源上避免阻塞渲染。
内容的提问来源于stack exchange,提问作者Shivam Puri
相关产品推荐
相关产品推荐

