JavaScript如何执行非阻塞语句 实现耗时函数不阻塞后续代码
这个需求完全可以实现。你遇到的阻塞问题本质是JavaScript默认单线程事件循环模型的特性:同步代码会按顺序占满调用栈执行,长耗时同步任务会阻塞后续所有代码、页面渲染和用户交互,只要选择合适的调度/多线程方案就能达到你要的执行效果。
1. 零成本顺序调整:宏任务延迟调度
如果你的核心诉求只是让当前同步代码先执行完,耗时函数本身计算量不大(执行时长<50ms,不会造成可感知的页面卡顿),直接把耗时函数丢到宏任务队列即可,不需要额外依赖:
function someBigFunctionIDontWantToWaitFor() { let i = 0; while (i < 100000000) { i++; } console.log("i =", i); } console.log("before call"); // 延迟0秒把函数加入宏任务队列,等当前同步调用栈清空后才会执行 setTimeout(someBigFunctionIDontWantToWaitFor, 0); console.log("after call");
运行后控制台输出完全符合你的预期:
before call after call i = 100000000
注意:这个方案没有把计算移出主线程,函数实际执行时如果计算量过大,依然会阻塞期间的用户操作和页面渲染。
2. 主线程友好调度:时间切片
如果不想开额外线程,又要避免长计算阻塞交互,可以把长任务拆成多个不超过10ms的小片段,每执行完一段就把主线程控制权交还给浏览器,等浏览器空闲了再执行下一段。可以用浏览器原生的scheduler.postTask或者requestIdleCallback实现调度,现代浏览器也支持用await scheduler.yield()直接让出线程:
async function someBigFunctionIDontWantToWaitFor() { let i = 0; const target = 100000000; while (i < target) { // 每跑100万次计数就让出一次主线程 const chunkEnd = Math.min(i + 1000000, target); while (i < chunkEnd) i++; // 交回控制权,让浏览器处理渲染、用户点击等事件 await scheduler.yield(); } console.log("i =", i); } console.log("before call"); someBigFunctionIDontWantToWaitFor(); console.log("after call");
这个方案既可以保证执行顺序符合预期,也不会让页面出现长时间无响应的情况,缺点是总计算耗时会比纯同步执行稍长。
3. 真·后台执行:Web Worker
如果是耗时几百毫秒以上的重计算任务(比如大数据处理、复杂算法运算),最稳妥的方案是用浏览器原生提供的Web Worker能力,把计算放到独立的后台线程运行,完全和主线程隔离,不会阻塞任何主线程的代码执行、页面渲染和用户交互。
不需要单独创建js文件也可以用内联方式实现:
console.log("before call"); // 内联定义Worker里要跑的代码 const workerScript = ` function someBigFunctionIDontWantToWaitFor() { let i = 0; while (i < 100000000) { i++; } console.log("i =", i); // 如果需要把计算结果传回主线程,可以调用postMessage // self.postMessage(i); } someBigFunctionIDontWantToWaitFor(); `; // 创建Worker线程并启动 const worker = new Worker(URL.createObjectURL( new Blob([workerScript], { type: "application/javascript" }) )); // 主线程监听Worker返回的结果(可选) // worker.onmessage = (e) => console.log("收到计算结果:", e.data); console.log("after call");
这个方案是唯一真正意义上的"后台执行",不管计算量多大,主线程都不会被阻塞。
不要误以为给函数加async关键字就能实现非阻塞:如果async函数内部是同步运行的长循环,哪怕前面加了await Promise.resolve(),也只是调整了执行顺序,函数实际运行时依然会占满主线程造成卡顿,和同步执行没有本质区别。
内容的提问来源于stack exchange,提问作者ThomasG2201

