async函数挂起点是引擎主动保存执行上下文并移交控制权的动作,不阻塞线程、不销毁上下文,仅在Promise处于pending时暂停执行流,将后续逻辑封装为微任务;若Promise已settled则直接继续,不挂起。

async 函数执行时的挂起点,不是“停在某一行代码”那么简单,而是引擎对当前函数执行上下文(FEC)的一次主动保存与移交控制权的动作。它不阻塞线程,也不销毁上下文,只是暂停执行流,并把后续逻辑交给微任务队列调度。
挂起点由 await 表达式触发,但本质是 Promise 状态切换的响应点
await 后面的表达式必须是一个 thenable(通常是 Promise)。引擎会立即求值该表达式,若结果已是 fulfilled 或 rejected,则不挂起,直接继续;只有当 Promise 处于 pending 状态时,才会真正挂起。
- 挂起发生在 await 表达式求值完成、但 Promise 尚未 settled 的瞬间
- 此时函数内部的词法环境、变量绑定、this 值、闭包引用全部保留在 FEC 中
- 引擎将 await 后面的剩余语句(包括 return、throw、console.log 等)封装为一个微任务回调,注册到微任务队列
挂起不等于退出,FEC 仍驻留内存并保持活跃引用
普通函数执行完就弹栈、FEC 被回收;而 async 函数在 await 处挂起后,FEC 并未出栈,只是被标记为“暂停态”,且持续持有对外部变量的引用——这正是闭包能正常工作的底层保障。
- 若 await 后面的代码访问了外层函数的局部变量,该变量不会被 GC 回收,因为 FEC 仍在内存中
- 多次 await 不会创建多个 FEC,始终复用同一个上下文对象
- 恢复执行时,堆栈帧重新激活,从挂起点下一行开始,而非重新初始化
挂起与恢复受事件循环严格调度,不受代码书写顺序干扰
挂起后控制权立刻交还给事件循环,后续同步代码(如 script end)、宏任务(如 setTimeout)会优先执行;只有当前宏任务结束、所有微任务清空后,才轮到 await 的回调执行。
立即学习“Java免费学习笔记(深入)”;
- 常见误区:以为 await 后面的代码“紧接着”执行——实际中间可能插入大量其他任务
- 挂起点的“暂停”是逻辑暂停,不是时间暂停;JS 引擎在此期间可处理其他任务
- 多个 await 连续出现时,每个都对应一次挂起+微任务注册,形成链式微任务调度
挂起点可被跳过:Promise 已 settle 时 await 不挂起
await 并非无条件暂停。如果右侧 Promise 已经处于 fulfilled 或 rejected 状态(例如 Promise.resolve(1)),引擎会直接取值并继续执行,不注册微任务,也不挂起 FEC。
- 这意味着同一段 async 代码,在不同运行条件下可能走完全不同的执行路径
- 这对性能有好处(避免不必要的微任务开销),但也要求开发者理解“挂起是条件行为”
- 调试时若发现 await 没生效,先检查 Promise 是否已被提前 resolve/reject


















