前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库then 回调异步执行
JavaScript异步与 Promise

Promise 已经兑现了,then 回调为什么还是异步执行?

then 回调会被放进微任务队列,等当前同步代码全部执行完才轮到它,Promise 是否已经兑现都一样。这样设计是为了让回调的执行时机永远一致,调用方不用猜它是同步还是异步。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#异步与 Promise#异步编程
先看核心答案读代码示例
理解线索

回调什么时候执行

  1. 同步代码当前调用栈里的代码,一口气执行完
  2. 微任务then、catch、finally、await 之后的代码,同步代码结束后立刻清空
  3. 宏任务setTimeout、事件回调等,每次只取一个,执行完再清空微任务

微任务队列会一直清空到底,微任务里又产生的微任务也会在本轮执行。如果不停地产生微任务,页面会卡住,连渲染都轮不到。

核心回答

先记住这个答案

因为规范规定 then 的回调必须放进微任务队列,等当前执行的同步代码全部结束后才调用,哪怕 Promise 在调用 then 之前就已经兑现。这样做是为了保证一致性:同一个 API 的回调,不能有时同步执行、有时异步执行,否则调用方后面的代码行为会跟着数据状态变来变去。微任务的执行时机又足够早,在当前同步代码结束后、下一个宏任务之前,所以不会带来明显延迟。

  • then 回调一律进微任务队列,从不同步执行
  • 已兑现的 Promise 也要等同步代码跑完
  • 目的是避免「有时同步有时异步」的回调
  • 微任务在下一个宏任务之前执行,比 setTimeout 早

已经兑现的 Promise 也要排队

先看一个最小的例子,p 在调用 then 之前就已经是 fulfilled 了。

回调总在同步代码之后JavaScript
const p = Promise.resolve(1) // 已经兑现

console.log('A')
p.then(() => console.log('C'))
console.log('B')
查看输出与解释
A
B
C

调用 p.then 的时候,结果明明已经在手上了,引擎也不会当场执行回调,而是把它放进微任务队列。等 console.log('B') 执行完、调用栈清空,才从队列里取出来执行。 微任务可以理解成「当前这件事做完后马上要做的小事」。浏览器每处理完一个宏任务(一段脚本、一次点击回调、一个定时器回调),都会先把微任务队列清空,再去做渲染和下一个宏任务。

为什么不允许同步执行

问题出在「有时同步有时异步」的回调上,社区管这种情况叫 Zalgo。下面是一个带缓存的回调风格函数,缓存命中时同步回调,没命中时异步回调。

时同步时异步的回调会出什么问题JavaScript
const cache = new Map()
function getUser(id, cb) {
  if (cache.has(id)) return cb(cache.get(id)) // 命中缓存:同步回调
  setTimeout(() => { cache.set(id, 'Tom'); cb('Tom') }, 10) // 未命中:异步回调
}

function load(label) {
  let loading = true
  getUser(1, () => console.log(label, 'loading =', loading))
  loading = false
}

load('第一次')
setTimeout(() => load('第二次'), 20)
查看输出与解释
第一次 loading = false
第二次 loading = true

同样的 load 函数,调用两次,回调里看到的 loading 却不一样。第一次缓存没命中,回调在 loading = false 之后才执行;第二次命中缓存,回调同步执行,loading 还是 true。真实项目里这种 bug 会随数据状态时有时无,很难复现。 Promise 把回调统一变成异步,就彻底消除了这类问题。不管数据是现成的还是要等网络,then 后面的同步代码一定先执行完,写代码的人只需要记一种情况。

为什么选微任务而不是 setTimeout
setTimeout 是宏任务,要排在其它定时器、事件回调后面,中间还可能插入一次渲染,而且嵌套多层后浏览器会把延迟至少拉到 4 毫秒。用微任务既能保证异步,又能在当前任务结束后立刻执行,一长串 then 链不会被拖慢。
回答前,多想一步

容易答错的地方

以为 Promise 里的代码也是异步的
new Promise(executor) 里的 executor 是同步执行的,传进去立刻就跑。异步的只是 then、catch、finally 的回调。「Promise 是异步的」这种笼统说法在面试里会被追问。
说 then 回调是宏任务
then 回调是微任务。区分它和 setTimeout 的关键就在这里:同一轮里,所有微任务都会在下一个 setTimeout 回调之前执行完。
以为 resolve 时回调马上执行
resolve 只是把已经登记的回调放进微任务队列,然后立刻返回。resolve 后面的同步代码会先执行,回调要等调用栈清空才跑。
试着用自己的话回答

面试官还会怎么问?

await 后面的代码是同步还是异步?

异步。await 相当于把后面的代码包进一个 then 回调,哪怕 await 的是普通值,比如 await 1,后面的代码也要等一个微任务才继续执行。

微任务里再产生微任务会怎样?

新产生的微任务会排进同一个队列,在本轮继续执行,直到队列为空才进入下一个宏任务。所以如果写了一个无限递归的 then 链或 queueMicrotask,页面会完全卡死,定时器和渲染都没有机会执行。

除了 Promise,还有哪些东西会产生微任务?

queueMicrotask 可以直接往队列里放一个微任务,MutationObserver 的回调也是微任务。在 Node.js 里还有 process.nextTick,它的优先级比 Promise 的微任务还高。

从一道题,走向一组知识

把知识连起来

异步与 Promise

怎么用 Promise.race 给异步操作加超时?超时后原来的 Promise 还会跑吗?

超时封装里定时器是宏任务、请求结果走微任务,理解执行时机才能写对 Promise.race 超时。

异步与 Promise

什么时候会触发 unhandledrejection?怎么避免未处理的 Promise 拒绝?

正因为回调是异步挂上的,引擎要等微任务跑完才能判断一个拒绝有没有被处理。

参考资料

  • JavaScript:异步与 Promise参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 已经兑现的 Promise 也要排队
  3. 为什么不允许同步执行
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑