当前位置:首页 > 技术文章 > 正文内容

async 函数的返回值到底是什么_async返回的是promise对象

zonemu1个月前 (09-04)技术文章16

async/await 已经成为处理异步操作的标配,它让我们能够用看似同步的方式书写异步代码,极大地提高了代码的可读性和可维护性。我们每天都在写 await fetch(...)

但:一个 async 函数,它的返回值究竟是什么?

场景一:返回一个非 Promise 值

这是最常见也最容易产生困惑的情况。我们来看一个最简单的 async 函数:

async function getNumber() {
  return 42; // 返回一个普通的数字
}

const result = getNumber();
console.log(result); 

如果你运行这段代码,控制台输出的并不会是 42,而是:

Promise { <pending> }

很快,这个 Promise 的状态会变为 fulfilled,并且其值为 42

背后发生了什么?

async 函数的 return 语句返回一个非 Promise 值(如数字、字符串、对象等)时,JavaScript 引擎会自动将其包装在一个 resolved状态的 Promise 中。换句话说,上面的代码在底层等价于:

function getNumber() {
  return Promise.resolve(42);
}

这就是为什么直接调用 getNumber() 会得到一个 Promise。为了获取到内部的值 42,我们必须使用 await 或者 .then() 来“解包”:

// 使用 await(必须在另一个 async 函数内)
async function main() {
    const num = await getNumber();
    console.log(num); // 输出: 42
}

main();

// 或者使用 .then()
getNumber().then(num => {
    console.log(num); // 输出: 42
});

场景二:返回一个 Promise

如果 async 函数本身就返回一个 Promise,情况会怎样?JavaScript 引擎会再把它包一层,变成 Promise<Promise<T>> 吗?

答案是:不会。

async 函数足够智能,如果它检测到返回值已经是一个 Promise,它会直接返回这个 Promise,而不会进行额外的包装。

async function fetchUser() {
 // 返回一个显式的 Promise
 return new Promise(resolve => {
    setTimeout(() => {
      resolve({ name: 'Alice' });
    }, 1000);
  });
}

const promise = fetchUser();
console.log(promise); // Promise { <pending> }

promise.then(user => {
 console.log(user); // 1秒后输出: { name: 'Alice' }
});

这个行为至关重要,它保证了 async 函数的返回值总是一个行为一致的、可 await 的对象,避免了不必要的 Promise 嵌套。

场景三:函数内部抛出错误

如果在 async 函数内部 throw 一个错误,会发生什么?程序会崩溃吗?

不一定。async 函数会将抛出的错误捕获,并将其作为 一个 rejected 状态的 Promise 返回。

async function willFail() {
    throw new Error('Something went wrong!');
}

const result = willFail();
console.log(result); // Promise { <pending> } -> 很快变为 -> Promise { <rejected> }

这个 rejected Promise 的 reason 就是我们抛出的那个 Error 对象。因此,我们可以用标准的 Promise 错误处理方式来捕获它:

// 使用 try...catch 配合 await
async function handleFailure() {
 try {
    await willFail();
  } catch (error) {
    console.error(error.message); // 输出: Something went wrong!
  }
}
handleFailure();

// 或者使用 .catch()
willFail().catch(error => {
 console.error(error.message); // 输出: Something went wrong!
});

这种机制将同步代码中的 try...catch 错误处理模型,无缝地融入到了异步流程控制中。

场景四:没有 return 语句

如果一个 async 函数执行完毕但没有 return 语句,它的返回值是什么?

和普通函数一样,没有 return 语句的函数会隐式地返回 undefined。根据场景一的规则,这个 undefined 会被 async 关键字包装成一个 resolved 状态的 Promise。

async function doNothing() {
  const a = 1 + 1;
  // 没有 return
}

doNothing().then(value => {
  console.log(value); // 输出: undefined
});

所以,即使函数什么都不返回,它依然遵循“永远返回一个 Promise”的黄金法则,只不过这个 Promise 的 resolved 值是 undefined

async/await 本质上是 Promise 的语法糖。它的设计初衷就是为了让开发者能够以更直观的方式处理异步逻辑。

async 的“包装”行为和 await 的“解包”行为,两者相辅相成,构成了这套优雅语法糖的核心。

相关文章

费用报销单填写及粘贴全攻略:避免常见错误!附费用报销管理系统

费用报销单是企业日常财务管理中的重要工具,用于记录和核销员工在工作中产生的各类费用。填写准确的费用报销单不仅能够保证财务报销流程的顺利进行,还能提高工作效率,确保公司资金的合理使用。在填写报销单时,员...

Garuda Linux:现代化、注重性能与美观的Linux发行版

什么是 Garuda Linux?Garuda Linux 是一个基于 Arch Linux 的现代化、注重性能与美观的桌面操作系统。它面向对性能有较高要求的用户,尤其受到 Linux 爱好者、游戏玩...

「2022」打算跳槽涨薪,必问面试题及答案——VUE篇

1、为什么选择VUE,解决了什么问题?vue.js 正如官网所说的,是一套构建用户界面的渐进式框架。与其它重量级框架不同的是,vue 被设计为可以自底向上逐层应用。vue 的核心库只关注视图层,不仅易...

vue3-内置组件-Teleport(vue内置指令有哪些)

Teleport<Teleport> 是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 结构外层的位置去。基本用法有时我们可能会遇到这样的场景:一个组件模板的一部...

前端React面试基础系列(React基础篇)

本文阅读8分钟,喜欢的小伙伴可以持续关系小编哦1. 什么是受控组件和非受控组件?受控组件像表单元素在用户输入时,像<input> <select>等元素需要绑定一个 chang...

程序员效率提升!使用自动化工具gitx,每周节约半小时

你是否经历过这样的折磨?一个 JIRA 需求要同时修复 dev、qa、staging 三个分支每个版本涉及 A、B、C 三个项目手动执行以下操作:从 dev 切临时分支cherry-pick 提交推送...