await非Promise值的执行顺序原理及await console.log场景解析
await 底层逻辑与非Promise值的执行顺序解析
问题场景
你原本认为await promise会把Promise推入微任务队列,等调用栈清空后再兑现,下面的代码结果符合预期:
async function func() { console.log(await Promise.resolve('bagel')); } func(); console.log('synchronous log'); // 输出顺序: // synchronous log // bagel
但当await非Promise值(比如await console.log)时,输出顺序发生了变化:
async function func() { await console.log('bagel'); } func(); console.log('synchronous log'); // 输出顺序: // bagel // synchronous log
想了解await的底层逻辑,以及非Promise值(尤其是await console.log场景)的执行顺序。
核心逻辑拆解
await的执行分为两个关键阶段:同步执行表达式 + 异步处理结果,这是理解顺序差异的核心:
第一步:同步执行await后面的表达式
不管await后面跟的是Promise还是普通值,表达式本身都会被同步执行,先拿到执行结果,再进入后续的异步等待逻辑。第二步:处理表达式结果
根据结果类型,await的处理逻辑不同:
- 如果结果是Promise对象:
await会暂停当前async函数,把函数剩余的代码(比如第一个例子里的console.log(...))包装成微任务,添加到微任务队列,然后让出执行权,回到调用栈继续执行同步代码。等调用栈清空后,微任务队列中的代码才会执行。 - 如果结果不是Promise对象:
JS引擎会自动把这个值包装成Promise.resolve(非Promise值),同样暂停当前async函数,把剩余代码包装成微任务放入队列。但此时表达式已经同步执行完毕,该产生的副作用(比如console.log的输出)已经发生了。
针对两个例子的具体分析
第一个例子:await Promise.resolve('bagel')
执行流程:
- 调用
func(),进入函数执行:- 先同步执行
Promise.resolve('bagel'),得到一个已兑现的Promise。 await识别到这是Promise,把console.log(...)包装成微任务放入队列,暂停func,回到全局调用栈。
- 先同步执行
- 全局调用栈执行
console.log('synchronous log'),输出synchronous log。 - 调用栈清空,执行微任务队列中的代码,输出
bagel。
第二个例子:await console.log('bagel')
执行流程:
- 调用
func(),进入函数执行:- 先同步执行
console.log('bagel'),直接输出bagel,该表达式返回undefined。 await把undefined包装成Promise.resolve(undefined),将func剩余代码(无实际逻辑)包装成微任务放入队列,暂停func,回到全局调用栈。
- 先同步执行
- 全局调用栈执行
console.log('synchronous log'),输出synchronous log。 - 调用栈清空,执行微任务队列中的空任务,无额外输出。
关键结论
await不会让后面的表达式变成异步执行,表达式本身永远是同步运行的。- 异步等待的是表达式的结果:如果是Promise,后续代码会延迟到微任务执行;如果是非Promise,只是把后续代码(哪怕是空)放到微任务,但表达式的副作用已经同步完成了。
await console.log(...)的场景中,console.log是同步执行的,所以会先输出日志,再执行后续的同步代码。
内容的提问来源于stack exchange,提问作者vietan
相关产品推荐
相关产品推荐

