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

async 函数里 return 一个值,外面拿到的是值还是 Promise?

拿到的永远是 Promise。async 函数会把 return 的值包进一个新的 Promise 里,想拿到值只能 await 它或者用 .then()。

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

`return` 什么,外面拿到什么

  1. 普通值包成以这个值兑现的 Promise,没写 return 就是 undefined
  2. Promise跟随它的状态,成功就拿到它的值,失败就一起失败
  3. thenable带 then 方法的对象也会被当成 Promise 展开

返回的 Promise 是引擎新建的,就算 return 的本来就是一个 Promise,两者用 === 比较也是 false。

核心回答

先记住这个答案

外面拿到的是 Promise,不是值。async 函数调用后会立刻返回一个新的 Promise,函数体里 return 的值会成为这个 Promise 的兑现值。如果 return 的本身就是一个 Promise,外层 Promise 会跟随它的状态,不会出现 Promise 套 Promise。要拿到里面的值,只能在另一个 async 函数里 await,或者调用 .then()。

  • async 函数的返回值一定是 Promise
  • return 42 等于返回一个以 42 兑现的 Promise
  • return 另一个 Promise 会被展开,不会套两层
  • 返回的是新 Promise,和 return 的那个不是同一个对象

先看代码和输出

很多人刚接触 async 时会写 const data = getData(),然后发现 data 打印出来是个 Promise。下面把这个现象放大看。

return 42 之后外面拿到了什么JavaScript
async function getNum() {
  return 42
}

const result = getNum()
console.log(result instanceof Promise) // true
console.log(result === 42) // false
result.then((v) => console.log('then 拿到', v))
console.log('同步结束')
查看输出与解释
true
false
同步结束
then 拿到 42

getNum() 一调用就返回了,返回的是 Promise。值要通过 .then() 拿,而 .then() 的回调是微任务,排在同步代码之后,所以 同步结束 先打印。 可以把 async 函数看成这样一层包装:function getNum() { return new Promise((resolve) => resolve(42)) }。这个写法只用来理解「为什么是 Promise」,它表达不了 await 的暂停,也没有自动捕获函数体里的 throw。

return 一个 Promise 会怎样

实际项目里 async 函数经常直接 return request(),这时返回的本身就是 Promise。外面不会拿到 Promise 套 Promise,而是被展开了。

返回的不是同一个 PromiseJavaScript
const inner = Promise.resolve('用户数据')

async function getUser() {
  return inner // return 一个已有的 Promise
}

const outer = getUser()
console.log(outer === inner) // false,引擎新建了一个
console.log(Promise.resolve(inner) === inner) // true,原样返回
outer.then((v) => console.log('拿到', v, typeof v))
查看输出与解释
false
true
拿到 用户数据 string

outer 是 async 函数自己新建的 Promise,它用 inner 去 resolve,于是跟随 inner 的状态,最后拿到的是字符串,不是 Promise。这和 Promise.resolve 不同:Promise.resolve 收到原生 Promise 会原样返回,async 函数永远新建。

thenable 也会被展开
展开规则不只认原生 Promise。只要返回的对象有 then 方法(这类对象叫 thenable),引擎就会调用它的 then 来取值。比如 return { then(resolve) { resolve(1) } },外面拿到的是 1。老项目里 jQuery 的 Deferred 能直接 await,靠的就是这条规则。反过来,如果你的业务对象恰好有个叫 then 的方法,从 async 函数返回它就会出怪问题。
回答前,多想一步

容易答错的地方

以为 await 能让函数「变成同步」,返回值就是普通值
await 只是暂停当前这个 async 函数内部的执行,函数对外依然是立刻返回 Promise。调用方如果不是 async 函数,不能 await,只能用 .then()。所以 async 具有传染性,一个底层函数改成 async,调用链上用到它结果的函数通常都要跟着改。
以为 return 一个 rejected Promise 会被当成普通值
return Promise.reject(err) 不会让外面拿到一个「装着失败的 Promise」作为值,外层 Promise 会直接变成 rejected,效果和 throw err 一样。区别只在于写在 try 里时,throw 会被本函数的 catch 接住,return 出去的 rejected Promise 不会。
以为返回的 Promise 就是 return 的那个
示例里已经看到 outer === inner 是 false。如果你在函数里给 inner 挂了自定义属性,或者拿返回值和缓存里的 Promise 做全等比较,都会对不上。
试着用自己的话回答

面试官还会怎么问?

async 函数没写 return 会返回什么?

返回一个以 undefined 兑现的 Promise。所以 await 一个没有 return 的 async 函数,拿到的是 undefined。

TypeScript 里 async 函数的返回类型怎么写?

必须写成 Promise<T>,比如 async function getNum(): Promise<number>。写成 number 会直接报错,因为 async 函数的返回值一定是 Promise。

能不能在普通的同步代码里拿到 async 函数的结果?

不能。Promise 的值要等微任务阶段才可用,同步代码跑完之前拿不到。在 ES Module 里可以用顶层 await,模块本身会等这个 Promise 完成,但这是把整个模块变成了异步,并不是把 Promise 变成了同步。

从一道题,走向一组知识

把知识连起来

异步与 Promise

forEach 里写 async/await,为什么不会一个一个按顺序等?

forEach 会丢掉 async 回调返回的 Promise,正是因为每个回调返回的都是 Promise 而不是值。

异步与 Promise

for 循环里逐个 await 请求是串行的吗?怎么改成并发?

拿到一组 async 函数返回的 Promise 后,是逐个 await 还是交给 Promise.all,决定了串行还是并发。

参考资料

  • JavaScript:异步与 Promise参考资料

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

本题目录
  1. 先记住这个答案
  2. 先看代码和输出
  3. return 一个 Promise 会怎样
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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