JavaScript中await搭配setTimeout执行顺序不符合预期问题
核心误区澄清
setTimeout本身是同步执行的Web/Node API,调用时会立刻将传入的回调注册到宏任务队列,同时返回一个数字类型的定时器ID,它的返回值不是Promise。- 当
await关键字后跟随的不是Promise对象时,JavaScript会自动将该值包装为一个*立即resolve(完成)*的Promise,等价于执行await Promise.resolve(非Promise值),这个过程完全不会等待setTimeout中注册的回调执行。
宏任务队列(回调队列)与微任务队列的执行流转
JavaScript是单线程执行语言,所有代码都靠事件循环调度执行,队列流转规则固定:
- 初始阶段优先执行整段同步脚本(这本身是第一个宏任务),执行过程中遇到异步任务会按类型分别推入对应队列
- 当前宏任务执行完毕后,会一次性清空微任务队列中的所有任务,执行过程中新产生的微任务也会在这一阶段被依次执行,直到微任务队列为空
- 从宏任务队列中取出一个到达可执行时间的最早任务,推入执行栈运行
- 单个宏任务执行完成后,回到第2步再次清空微任务队列
- 重复3-4步的循环,直到两个队列全部清空
常见任务分类:
- 宏任务(回调队列存储的任务):整段script脚本、
setTimeout/setInterval回调、I/O操作、UI渲染任务- 微任务:
Promise.then/catch/finally回调、await后的执行代码(本质是Promise.then的回调)、queueMicrotask添加的任务
两段测试代码的执行流程拆解
带await的版本
原代码:
async function foo() { console.log('FIrst'); await setTimeout(()=>{console.log('timeout')},1000); console.log('Second'); } foo(); console.log('Three');
逐步骤执行逻辑:
- 调用
foo()进入函数内部,执行同步代码console.log('FIrst'),输出FIrst - 同步执行
setTimeout调用:将打印timeout的回调注册到宏任务队列,标记1000ms后可执行,同时返回数字类型的定时器ID - 遇到
await 定时器ID:由于await后跟的是数字非Promise,自动包装为立即resolve的Promise,async函数在此处暂停执行,将await后续的console.log('Second')推入微任务队列,跳出foo函数回到全局上下文 - 执行全局同步代码
console.log('Three'),输出Three - 至此首个宏任务(整段同步脚本)执行完毕,开始清空微任务队列:取出
console.log('Second')执行,输出Second - 微任务队列清空后,事件循环轮询宏任务队列,1000ms计时结束后取出setTimeout注册的回调执行,输出
timeout
最终输出顺序和运行结果完全一致:FIrst->Three->Second->timeout
不带await的版本
原代码:
function foo() { console.log('FIrst'); setTimeout(()=>{console.log('timeout')},1000); console.log('Second'); } foo(); console.log('Three');
逐步骤执行逻辑:
- 调用
foo()进入函数内部,执行console.log('FIrst'),输出FIrst - 同步执行
setTimeout调用,将打印timeout的回调注册到宏任务队列后,没有await阻断,直接继续执行后续同步代码 - 执行
console.log('Second'),输出Second - foo函数执行完毕回到全局,执行
console.log('Three'),输出Three - 同步代码执行完毕,微任务队列为空,事件循环轮询宏任务队列,1000ms后取出setTimeout回调执行,输出
timeout
最终输出顺序:FIrst->Second->Three->timeout
正确等待setTimeout执行的写法
如果需要等待setTimeout的回调执行完成后再运行后续代码,必须手动将setTimeout包装为Promise,在回调逻辑执行完成后调用resolve,才能让await正确等待异步操作完成:
async function foo() { console.log('FIrst'); await new Promise(resolve => { setTimeout(()=>{ console.log('timeout'); resolve(); },1000); }); console.log('Second'); } foo(); console.log('Three');
该版本的输出会符合最初的预期:
FIrst Three timeout Second
内容的提问来源于stack exchange,提问作者Tinfinity
相关产品推荐
相关产品推荐

