JavaScript async/await:事件循环与执行顺序详解

这门课面向已经在写 JavaScript 的开发者:你会用 .then(),会写 async/await,也会调用 setTimeout,但代码的执行顺序在你眼里仍然像魔法。你把 A 写在 B 前面,B 却先打印;一个你最后才写的 .then(),不知怎么就抢在了更早写的 setTimeout(fn, 0) 前面;你说不清 await 到底对它下面那行代码做了什么。解法不是再学一遍语法,而是一套心智模型:一个调用栈、一个微任务队列、一个任务队列,外加一个决定谁下一个运行的事件循环。一旦你能看见这三个地方,执行顺序就不再是魔法,而是可以在纸上推算出来的东西。

课程结束后,你将能够(课程目标):

  • 描述调用栈和运行至完成规则,并解释为什么同步代码总是先于任何被推迟的回调执行完。
  • 说出三个核心部件:调用栈、任务队列、事件循环,并说明 setTimeout 的回调在哪里等待、由谁来运行。
  • 用微任务队列及其「先彻底清空再取下一个任务」的规则,解释为什么 promise 的 .then() 回调会先于 setTimeout(fn, 0) 运行。
  • 解释 await 实际做了什么:它只挂起自己所在的 async 函数,并把剩余部分安排成一个微任务,而不会阻塞线程。
  • 预测一段混合了同步打印、setTimeout.then()await 的非平凡代码片段的精确输出顺序,然后在真实的 Node 或浏览器控制台里验证。

先修条件: 你会写 JavaScript 函数,会调用 console.logsetTimeout,并且至少在语法层面用过 .then()async/await。不要求任何事件循环知识,不要求读过 HTML 规范,也不假设任何框架或构建工具。

练习环境: 你自己在 Node(node file.mjs)或浏览器 DevTools 控制台里运行这些代码片段,本课程没有内嵌运行时。每次预测都以你运行真实代码、对照自己的判断收尾。

课程目录

课号主题你将学到
01调用栈与运行至完成调用栈、单线程与运行至完成:为什么 setTimeout(fn, 0) 要等
02任务队列与事件循环任务(宏任务)队列,以及在栈空时取任务的事件循环
03微任务队列与执行优先级微任务队列,为什么 .then() 是微任务,以及它为何先于 setTimeout(fn, 0) 运行
04await 的执行机制async 函数同步运行到第一个 await;剩余部分变成微任务;await 从不阻塞
05执行顺序的预测与验证一套可重复的流程,推算任意代码片段的输出顺序,并在真实控制台验证

来源列表见 sources.md