264 Returning Values from Async Functions 学习笔记

一、核心知识点总览

本视频聚焦 JavaScript 中异步函数(Async Functions)返回值的特性、获取方式、错误处理及代码优化,解决“异步函数如何返回数据”“返回值为何是 Promise”“如何优雅处理异步返回结果”等关键问题,是异步编程进阶的核心内容。

二、异步函数返回值的本质(核心特性)

2.1 关键结论:异步函数永远返回 Promise

  • 特性说明:无论异步函数(async function 声明)内部返回什么值(基本类型、对象、函数等),最终都会被 JavaScript 包装成一个 已兑现(fulfilled)的 Promise;若函数内部抛出错误,则返回一个 已拒绝(rejected)的 Promise
  • 原理拆解
    异步函数的执行逻辑是“非阻塞”的——调用异步函数时,JS 引擎会立即返回一个未完成的 Promise,然后在后台继续执行函数内部代码;当函数执行完毕(或遇到 return/throw),再更新 Promise 的状态(兑现/拒绝)并传递结果。

2.2 对比:异步函数 vs 普通函数返回值

通过 console.log 观察执行顺序,可直观理解两者差异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 普通函数:同步执行,立即返回值
function normalFunc() {
console.log("普通函数内部");
return "普通函数返回值";
}
console.log("1. 调用普通函数前");
const normalResult = normalFunc();
console.log("2. 普通函数返回值:", normalResult);
// 执行顺序:1 → 普通函数内部 → 2 → 普通函数返回值:普通函数返回值

// 2. 异步函数:先返回Promise,内部代码后台执行
async function asyncFunc() {
console.log("异步函数内部");
return "异步函数返回值"; // 会被包装成 Promise.resolve("异步函数返回值")
}
console.log("3. 调用异步函数前");
const asyncResult = asyncFunc();
console.log("4. 异步函数返回值:", asyncResult);
// 执行顺序:3 → 4 → 异步函数内部(后台执行完毕后)
// 异步函数返回值:Promise {<fulfilled>: "异步函数返回值"}

三、异步函数返回值的获取方式

3.1 方式1:使用 .then() 链式调用(兼容 Promise 旧语法)

  • 适用场景:需要在异步函数执行完毕后,基于返回值做后续操作(如渲染页面、调用其他接口)。

  • 语法示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 异步函数:模拟获取用户地理位置(返回城市名称)
    async function getCity() {
    // 模拟异步操作(如调用地理定位API)
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return "葡萄牙 Olhao"; // 包装为 Promise.resolve("葡萄牙 Olhao")
    }

    // 调用异步函数,用 .then() 获取返回值
    console.log("开始获取城市...");
    getCity()
    .then((city) => {
    console.log("获取到城市:", city); // 1秒后执行:获取到城市:葡萄牙 Olhao
    })
    .catch((error) => {
    console.error("获取失败:", error);
    });

3.2 方式2:使用 await + 异步函数(推荐,更简洁)

  • 核心规则await 关键字必须在 另一个异步函数内部 使用,作用是“暂停当前异步函数的执行,等待目标 Promise 兑现后再继续”。

  • 优化点:避免 .then() 链式调用的嵌套问题(“回调地狱”),代码逻辑更接近同步写法。

  • 语法示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    async function getCity() {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return "葡萄牙 Olhao";
    }

    // 用 async IIFE(立即执行异步函数)包裹 await(因顶层 await 需特殊配置)
    (async function () {
    console.log("开始获取城市...");
    try {
    const city = await getCity(); // 等待 getCity() 返回的 Promise 兑现
    console.log("获取到城市:", city); // 1秒后执行:获取到城市:葡萄牙 Olhao
    } catch (error) {
    console.error("获取失败:", error);
    }
    })();
  • 补充:若项目支持 ES2022+ 语法,可直接在模块顶层使用 await(无需 IIFE),如:

    1
    2
    3
    // 模块文件(.mjs 或 package.json 配置 "type": "module")
    const city = await getCity();
    console.log(city);

四、异步函数的错误处理

4.1 问题:异步函数内部错误默认不触发外部 catch

  • 现象:若异步函数内部用 try/catch 捕获了错误,但未重新抛出(throw),则函数返回的 Promise 仍为“已兑现”状态,外部 .catch()try/catch 无法捕获该错误。

  • 示例(错误写法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    async function getCity() {
    try {
    await new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error("定位失败")), 1000); // 模拟异步错误
    });
    return "葡萄牙 Olhao";
    } catch (error) {
    console.log("内部捕获错误:", error.message); // 仅内部打印,未重新抛出
    }
    }

    // 外部调用:无法捕获内部错误
    getCity()
    .then((city) => {
    console.log("城市:", city); // 执行:城市:undefined(因函数未返回有效值)
    })
    .catch((error) => {
    console.error("外部捕获错误:", error); // 不执行!
    });

4.2 解决方案:重新抛出错误(throw error

  • 原理:在异步函数的 catch 块中重新抛出错误,会将函数返回的 Promise 状态改为“已拒绝”,外部即可通过 .catch()try/catch 捕获。

  • 示例(正确写法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    async function getCity() {
    try {
    await new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error("定位失败")), 1000);
    });
    return "葡萄牙 Olhao";
    } catch (error) {
    console.log("内部捕获错误:", error.message);
    throw error; // 重新抛出,让外部能捕获
    }
    }

    // 外部用 try/catch + await 捕获错误
    (async function () {
    try {
    const city = await getCity();
    console.log("城市:", city);
    } catch (error) {
    console.error("外部捕获错误:", error.message); // 执行:外部捕获错误:定位失败
    }
    })();

4.3 额外优化:用 finally 执行收尾操作

  • 特性finally 块中的代码无论 Promise 状态是“兑现”还是“拒绝”,都会执行(如关闭加载动画、清理资源)。
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    (async function () {
    console.log("开始获取城市...");
    try {
    const city = await getCity();
    console.log("城市:", city);
    } catch (error) {
    console.error("错误:", error.message);
    } finally {
    console.log("获取操作结束(无论成功/失败)"); // 必执行
    }
    })();
    // 执行顺序:
    // 1. 开始获取城市...
    // 2. 内部捕获错误:定位失败
    // 3. 错误:定位失败
    // 4. 获取操作结束(无论成功/失败)

五、实战例题(巩固应用)

例题1:获取用户信息并渲染

需求:用两个异步函数分别获取“用户 ID”和“用户详情”,要求先获取 ID,再用 ID 查详情,最后打印结果;若任一环节失败,捕获错误并提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 异步函数1:获取用户ID(模拟接口调用)
async function getUserId() {
await new Promise((resolve) => setTimeout(resolve, 500));
return "user_123"; // 模拟返回ID
}

// 异步函数2:用ID获取用户详情
async function getUserInfo(userId) {
try {
await new Promise((resolve) => setTimeout(resolve, 500));
// 模拟错误:若ID无效则抛出错误
if (userId !== "user_123") throw new Error("用户不存在");
return { name: "张三", age: 25, city: "北京" };
} catch (error) {
throw new Error(`获取详情失败:${error.message}`); // 重新抛出,附加上下文
}
}

// 主逻辑:串联两个异步函数
(async function renderUser() {
try {
console.log("开始加载用户信息...");
const userId = await getUserId();
const userInfo = await getUserInfo(userId);
console.log("用户信息:", userInfo);
// 执行:用户信息:{ name: '张三', age: 25, city: '北京' }
} catch (error) {
console.error("加载失败:", error.message);
} finally {
console.log("用户信息加载流程结束");
}
})();

例题2:判断返回值类型

问题:以下代码执行后,result1result2 分别是什么类型?为什么?

1
2
3
4
5
6
7
8
9
async function func1() {
return 123;
}
async function func2() {
throw new Error("出错了");
}

const result1 = func1();
const result2 = func2();

答案

  • result1Promise 类型(状态:fulfilled,值:123)。因为异步函数无论返回什么值,都会包装成已兑现的 Promise。
  • result2Promise 类型(状态:rejected,值:Error 对象)。因为异步函数内部抛出错误,会包装成已拒绝的 Promise。

六、总结

  1. 返回值本质:异步函数永远返回 Promise,return X 等价于 return Promise.resolve(X)throw E 等价于 return Promise.reject(E)
  2. 获取方式:优先用 await + async(代码简洁),兼容场景用 .then()await 必须在异步函数内部使用。
  3. 错误处理:异步函数内部捕获错误后,需 throw error 才能让外部捕获;finally 用于执行收尾操作。
  4. 核心原则:避免混合 await.then() 语法,保持代码风格统一,降低可读性成本。