详解 TypeScript 中,async 和 await

在 TypeScript 中,asyncawait 是处理异步操作的语法糖 (Syntactic Sugar),本质上它们仍然是基于 Promise 的。

但在 TypeScript 的语境下,理解它们的核心在于掌握 返回类型推断解包(Unwrapping) 机制。

以下是 asyncawait 的详细解析:


1. async 关键字

async 用于修饰函数。它有两个核心规则:

  1. 强制返回 Promise :无论函数内部 return 什么,最终都会被封装成一个 Promise。
  2. 类型注解 :在 TypeScript 中,async 函数的返回值类型必须明确写成 Promise<T>

示例代码

typescript 复制代码
// 1. 自动推断
async function simple() {
  return 123; 
}
// TS 自动推断出 simple() 的返回类型是: Promise<number>


// 2. 显式注解 (推荐)
// 即使你 return 的是一个普通字符串,类型定义必须包裹在 Promise 中
async function getUserName(): Promise<string> {
  return "Alice";
}

// 3. 无返回值的异步函数
async function logData(): Promise<void> {
  console.log("Saving...");
  // 相当于 return Promise.resolve(undefined);
}

注意 :你不能将 async 函数的返回类型写成 stringnumber,必须是 Promise<string>


2. await 关键字

await 只能在 async 函数内部使用(或者在支持 Top-level await 的模块中使用)。

它的作用是:

  1. 暂停执行:暂停当前 async 函数的执行,直到 Promise 状态变为 resolved 或 rejected。
  2. 自动解包 (Unwrapping) :这是 TS 的强大之处。如果一个表达式是 Promise<string>await 之后得到的结果类型就是 string

类型解包示例

typescript 复制代码
function fetchAge(): Promise<number> {
  return Promise.resolve(18);
}

async function main() {
  // TS 知道 fetchAge 返回 Promise<number>
  // 所以 age 被自动推断为 number 类型
  const age = await fetchAge(); 
  
  console.log(age + 1); // 这里的加法是合法的,因为 age 是 number
}

3. 错误处理 (try...catch)

await 后面的 Promise 被 reject 时,会抛出一个异常。因此,标准做法是使用 try...catch

在 TypeScript 中,catch(error) 中的 error 默认类型通常是 unknownany

typescript 复制代码
async function requestData(): Promise<string> {
  throw new Error("网络连接断开");
}

async function handleTask() {
  try {
    const data = await requestData();
    console.log(data);
  } catch (error) {
    // 这里的 error 类型是 unknown (在 TS 4.4+ 开启 useUnknownInCatchVariables 时)
    
    // 最佳实践:进行类型守卫
    if (error instanceof Error) {
      console.error("错误消息:", error.message);
    } else {
      console.error("未知错误:", error);
    }
  }
}

4. 串行 vs 并行 (性能关键)

这是使用 async/await 最容易踩的坑。await 会阻塞后续代码,如果多个请求之间没有依赖关系,不要写成串行。

❌ 串行写法 (慢)

typescript 复制代码
async function serial() {
  const start = Date.now();
  // 假设 getUser 耗时 1s, getPosts 耗时 1s
  const user = await getUser();   // 等待 1s
  const posts = await getPosts(); // 再等待 1s
  // 总耗时: 2s
}

✅ 并行写法 (快 - 使用 Promise.all)

typescript 复制代码
async function parallel() {
  // 同时启动两个任务
  const userPromise = getUser();
  const postsPromise = getPosts();

  // 等待它们全部完成
  // TS 会自动推断 results 类型为 [User, Post[]]
  const [user, posts] = await Promise.all([userPromise, postsPromise]);
  // 总耗时: 1s
}

5. 高级用法与技巧

5.1 异步箭头函数

typescript 复制代码
const login = async (username: string): Promise<boolean> => {
  return true;
};

5.2 Top-level Await (顶层 await)

在现代 TypeScript (ES2022 / ESNext 且 module 设置为 esnext/system 等) 中,可以在文件最外层直接使用 await,不需要包裹在 async function 中。这在初始化数据库连接等场景很有用。

typescript 复制代码
// db.ts
import { createConnection } from 'typeorm';

// 直接等待连接建立
export const connection = await createConnection();

5.3 Promise<void> 的陷阱

有时候我们会把 async 函数作为回调传给 forEach,这通常是不对的,因为 forEach 不会等待 async 回调完成。

typescript 复制代码
const ids = [1, 2, 3];

// ❌ 错误做法:forEach 不会等待 await
ids.forEach(async (id) => {
  await fetchUser(id); 
});
console.log('Done'); // 这行代码会在 fetchUser 完成之前就打印!

// ✅ 正确做法:使用 map + Promise.all
await Promise.all(ids.map(async (id) => {
  await fetchUser(id);
}));
console.log('Done'); // 确实等待所有都完成了

总结 TS 中的 Async/Await

特性 说明 对应的 TS 写法
定义 函数必须返回 Promise async function fn(): Promise<T> { ... }
调用 必须在 async 函数内 const val: T = await promise;
返回值 自动包装 return T 会变成 Promise<T>
异常 使用 try-catch 捕获 catch 块中的变量需注意类型检查

掌握这套机制后,你的异步代码会像同步代码一样清晰,同时拥有完整的类型检查保护。

相关推荐
未来之窗软件服务4 小时前
一体化系统(九)智慧社区综合报表——东方仙盟练气期
大数据·前端·仙盟创梦ide·东方仙盟·东方仙盟一体化
陈天伟教授7 小时前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看8 小时前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai8 小时前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端
苏打水com8 小时前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅8 小时前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com8 小时前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger8 小时前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite
BD_Marathon9 小时前
【JavaWeb】路径问题_前端绝对路径问题
前端