同步方法内使用.then调用异步API阻塞执行失效问题
问题根因
你之前的死循环阻塞方案死锁的判断完全准确:JavaScript基于单线程事件循环模型,只要同步执行栈中的代码(比如你写的空while循环)没有执行完成,微任务队列(Promise.then回调属于微任务)、宏任务队列中的所有异步回调都没有机会被调度执行,相当于你直接堵死了AsyncMethod内部异步逻辑的执行路径,runningAsync标识永远不会被修改,最终必然挂起。
可行解决方案
按优先级从高到低尝试:
- 优先确认第三方控件的原生异步支持
绝大多数支持前置拦截事件的流程类控件,本身已经做了异步兼容:如果事件回调函数返回Promise,控件会自动等待Promise状态变更后,再根据resolve的结果判断是否允许跳转,不需要手动实现阻塞。你可以直接在事件回调中返回async校验函数的结果(注意不要在回调内部自行处理异步逻辑不返回,要把Promise直接return给控件),测试是否能正常阻塞等待。 - 无原生异步支持时,根据运行环境选择非忙等的同步阻塞方案
绝对不要使用空while循环做忙等阻塞,会100%占满线程导致死锁:- 浏览器环境:使用
Web Worker + SharedArrayBuffer + Atomics实现无忙等阻塞。将异步权限校验逻辑放到Web Worker中执行,主线程通过Atomics.wait()同步等待Worker的执行结果,该API阻塞时不会卡住事件循环调度,Worker内的异步逻辑可以正常执行,等Worker拿到结果后通过共享内存写入结果并唤醒主线程即可。
主线程示例代码:
Worker侧示例代码:// 前置:需要服务端配置跨域隔离头,才能使用SharedArrayBuffer const sab = new SharedArrayBuffer(4); const int32 = new Int32Array(sab); const worker = new Worker('./auth-check-worker.js'); // 跳转前事件回调 function beforeStageJump() { // 先重置共享内存状态 Atomics.store(int32, 0, 0); // 把校验需要的参数、共享内存引用传给worker worker.postMessage({ sab, userInfo: currentUser, targetStage: nextStageId }); // 同步等待,直到worker写入结果唤醒 Atomics.wait(int32, 0, 0); const checkResult = Atomics.load(int32, 0); // checkResult为1代表有权限放行,-1代表无权限拦截 return checkResult === 1; }// auth-check-worker.js self.onmessage = async (e) => { const { sab, userInfo, targetStage } = e.data; const int32 = new Int32Array(sab); try { // 调用你的异步权限校验API const hasPermission = await asyncCheckPermission(userInfo, targetStage); Atomics.store(int32, 0, hasPermission ? 1 : -1); } catch (err) { Atomics.store(int32, 0, -1); } // 唤醒主线程 Atomics.notify(int32, 0); } - Node.js环境:直接使用
deasync类的原生扩展实现同步等待,这类扩展会在等待Promise时底层驱动事件循环执行,不会卡住异步回调:const deasync = require('deasync'); // 包装异步方法为同步方法 const syncCheckPermission = deasync(async (userInfo, targetStage, cb) => { try { const res = await asyncCheckPermission(userInfo, targetStage); cb(null, res); } catch (err) { cb(err); } }); // 事件回调中直接同步调用即可 function beforeStageJump() { return syncCheckPermission(currentUser, nextStageId); }
- 浏览器环境:使用
- 环境受限无法使用上述阻塞方案时,用交互逻辑绕开
如果页面无法配置跨域隔离头(SharedArrayBuffer依赖跨域隔离),也无法使用原生扩展,可以直接调整交互逻辑:- 跳转前事件触发时,同步阻止本次默认跳转
- 展示全局加载态,调用异步权限校验接口
- 校验通过则手动调用控件提供的
nextStage/跳转API进入下一阶段,校验不通过则关闭加载态、弹出无权限提示
该方案兼容性最好,不需要修改底层执行逻辑,绝大多数业务场景都可落地。
内容的提问来源于stack exchange,提问作者Sven Peeters
相关产品推荐
相关产品推荐

