如何在JavaScript中异步执行耗时函数且不使用setTimeout?
不使用setTimeout实现耗时函数的异步执行
问题背景
现有如下代码,当前执行后输出顺序为 starts..., waiting ends..., ends...,需要修改为 starts..., ends..., waiting ends...,且不能使用setTimeout:
function wait() { let a = Date.now() while (Date.now() - a < 3000) { // 阻塞循环 } console.log('waiting ends...') } function start(){ console.log('starts...') wait() } function end(){ console.log('ends...') } start() end()
解决方案
核心思路是利用JavaScript事件循环机制,将wait()放入微任务队列或宏任务队列,让它在当前同步代码(包括end())执行完毕后再运行,避免阻塞主线程。
方案1:Promise + 微任务队列
将wait()挂载到Promise.resolve().then()的回调中,微任务会在同步代码执行完成后立即执行:
function wait() { let a = Date.now() while (Date.now() - a < 3000) { // 阻塞循环 } console.log('waiting ends...') } function start(){ console.log('starts...') Promise.resolve().then(wait); } function end(){ console.log('ends...') } start() end()
方案2:requestIdleCallback(宏任务)
requestIdleCallback会在浏览器主线程空闲时触发回调,属于宏任务范畴,同样能实现延后执行:
function wait() { let a = Date.now() while (Date.now() - a < 3000) { // 阻塞循环 } console.log('waiting ends...') } function start(){ console.log('starts...') requestIdleCallback(wait); } function end(){ console.log('ends...') } start() end()
方案3:MessageChannel(宏任务)
通过MessageChannel的消息通信机制,将回调加入宏任务队列:
function wait() { let a = Date.now() while (Date.now() - a < 3000) { // 阻塞循环 } console.log('waiting ends...') } function start(){ console.log('starts...') const channel = new MessageChannel(); channel.port1.onmessage = wait; channel.port2.postMessage(null); } function end(){ console.log('ends...') } start() end()
原理说明
JavaScript事件循环的执行顺序为:
- 优先执行当前所有同步任务(即
start()和end()中的同步打印逻辑) - 同步任务执行完毕后,清空所有微任务队列中的任务
- 最后依次执行宏任务队列中的任务
以上方案均通过将wait()放入微/宏任务队列,确保end()的同步代码先执行,从而达到预期的输出顺序。
内容的提问来源于stack exchange,提问作者Li Chen
相关产品推荐
相关产品推荐

