前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库Promise 嵌套展开
JavaScript异步与 Promise

then 回调里返回一个 Promise,下一个 then 拿到的是 Promise 还是值?

拿到的是值。then 回调返回一个 Promise 时,then 生成的新 Promise 不会立刻成功,而是等这个内层 Promise 完成,再跟着它变成同样的状态、拿到同样的值。所以 Promise 链上永远不会出现「Promise 套 Promise」。

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

外层 Promise 跟随内层的状态

  1. 跟随外层 Promise 不自己决定结果,等内层完成后复制它的状态和值
  2. thenable任何有 then 方法的对象,都会被调用 then 来取值
  3. 递归展开内层完成的值如果还是 Promise,会继续展开,直到拿到普通值

展开只发生在 resolve 这条路上。reject(promise) 不会展开,catch 收到的就是那个 Promise 对象本身。

核心回答

先记住这个答案

下一个 then 拿到的是值,不是 Promise。then 每次调用都会生成一个新的 Promise,回调返回普通值时,这个新 Promise 直接以该值成功;回调返回一个 Promise 时,新 Promise 会先等着,等内层 Promise 完成后跟随它的状态:内层成功,外层就以同样的值成功;内层失败,外层就以同样的原因失败。这个过程叫展开,也是 Promise 链能把多个异步操作排成一条直线、不用层层嵌套的原因。

  • then 返回 Promise,下一步拿到的是它的最终值
  • 内层失败,外层也失败,错误往后传
  • 带 then 方法的对象也会被当成 Promise 展开
  • 回调返回自己所在的 Promise 会报 TypeError

返回 Promise 时链会等它

最常见的场景是先请求用户信息,再用结果请求订单。第二个请求在 then 里 return 出去,下一个 then 就能直接拿到订单数据。

链会等内层 Promise 完成JavaScript
function delay(ms, value) {
  return new Promise((resolve) => setTimeout(() => resolve(value), ms))
}

Promise.resolve('start')
  .then(() => delay(100, '用户数据')) // 返回一个 100 毫秒后才完成的 Promise
  .then((v) => {
    console.log('拿到:', v, typeof v) // 拿到的是字符串,不是 Promise
    return Promise.reject(new Error('权限不足')) // 返回一个失败的 Promise
  })
  .then(() => console.log('不会执行'))
  .catch((err) => console.log('catch 到:', err.message))
查看输出与解释
拿到: 用户数据 string
catch 到: 权限不足

第二个 then 的回调在 100 毫秒后才执行,说明链确实在等 delay 返回的 Promise。第二个 then 返回了一个失败的 Promise,外层也跟着失败,跳过了中间的 then,直接进 catch。 如果忘了写 return,比如写成 .then(() => { delay(100, '用户数据') }),回调返回的是 undefined,外层 Promise 立刻以 undefined 成功,链不会等这个请求,下一步拿到的也是 undefined。这是 Promise 链里最常见的 bug。

展开是怎么判断的

外层 Promise 拿到回调的返回值后,会检查它是不是一个 thenable,也就是带有 then 方法的对象。是的话,就调用它的 then,把自己的 resolve 和 reject 传进去,让对方来决定自己的结果。所以展开不要求返回值是原生 Promise,只要长得像就行。

自定义 thenable 也会被展开JavaScript
const p = Promise.resolve(1)
console.log(Promise.resolve(p) === p) // 传入原生 Promise,直接原样返回

const thenable = {
  then(onFulfilled) {
    console.log('then 被调用')
    onFulfilled(42)
  }
}

Promise.resolve()
  .then(() => thenable)
  .then((v) => console.log('展开得到:', v))
查看输出与解释
true
then 被调用
展开得到: 42

这个设计是为了兼容。ES6 之前社区有很多 Promise 库,比如 Bluebird、Q,它们的实例都不是原生 Promise,但都有 then 方法。按 thenable 判断,这些库的对象可以直接混进原生 Promise 链里用。

展开是递归的
如果内层 Promise 完成的值本身又是一个 Promise,外层会继续等下去,直到拿到一个不是 thenable 的值为止。所以不管套多少层,最后到手的一定是普通值。反过来说,你没法让一个 Promise 以另一个 Promise 作为成功值,TypeScript 里 await 表达式的类型用 Awaited<T> 递归解包,就是在描述这个行为。
回答前,多想一步

容易答错的地方

以为 reject 一个 Promise 也会展开
只有成功这条路会展开。reject(somePromise) 或者 Promise.reject(somePromise) 会把这个 Promise 对象本身当作失败原因,catch 里拿到的是 Promise,不是它的值。
以为返回 Promise 和返回值一样快
返回普通值时,下一个 then 回调在下一个微任务就能执行。返回原生 Promise 时,要先安排一个任务去调用它的 then,再等它的回调通知回来,大约多出两个微任务。单独看无所谓,但在比较打印顺序的题里,这会让「返回 Promise」的那条链落后于其他链。
在 then 里嵌套 then
写成 .then((u) => { getOrders(u).then((o) => { ... }) }) 又回到了回调地狱,而且外层链不会等里面的请求,错误也传不出来。正确写法是 return getOrders(u),在外面接下一个 then。
试着用自己的话回答

面试官还会怎么问?

then 回调返回自己所在的那个 Promise 会怎样?

比如 const p = Promise.resolve().then(() => p),外层要等 p 完成,而 p 就是外层自己,永远等不到。规范专门规定了这种情况,p 会以 TypeError 拒绝,Chrome 里的错误信息是 Chaining cycle detected for promise。

new Promise((resolve) => resolve(otherPromise)) 也会展开吗?

会。展开的逻辑在 resolve 函数里,不管是 executor 里的 resolve、then 回调的返回值,还是 Promise.resolve,走的都是同一套判断。区别只是 Promise.resolve 收到原生 Promise 时会直接返回它本身,不再包一层。

await 一个 thenable 会怎样?

await 内部相当于用 Promise.resolve 包一下再等它,所以同样会调用这个对象的 then 方法并等它回调。如果这个对象的 then 永远不调用传进来的函数,await 就会一直卡住。

从一道题,走向一组知识

把知识连起来

异步与 Promise

Promise.race 传一个空数组进去,会返回什么?

外层 Promise 会一直等内层完成,如果内层是 Promise.race([]) 这种永远 pending 的,整条链就卡住了。

异步与 Promise

多个 Promise 并发执行时,then 回调一定按完成顺序触发吗?

返回 Promise 会多走几次微任务,这正是打印顺序题里 then 触发顺序常见的坑。

参考资料

  • JavaScript:异步与 Promise参考资料

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

本题目录
  1. 先记住这个答案
  2. 返回 Promise 时链会等它
  3. 展开是怎么判断的
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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