详解 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 块中的变量需注意类型检查

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

相关推荐
小皮虾43 分钟前
告别服务器!小程序纯前端“图片转 PDF”工具,隐私安全又高效
前端·javascript·微信小程序
ohyeah44 分钟前
我的变量去哪了?JS 作用域入门指南
前端·javascript
倚栏听风雨1 小时前
TypeScript 中,Promise
前端
影i1 小时前
Vue 3 踩坑实录:如何优雅地把“上古”第三方插件关进 Iframe 小黑屋
前端
小明记账簿_微信小程序1 小时前
vue项目中使用echarts做词云图
前端
浪浪山_大橙子1 小时前
Trae SOLO 生成 TensorFlow.js 手势抓取物品太牛了 程序员可以退下了
前端·javascript
出征1 小时前
Pnpm的进化进程
前端
屿小夏1 小时前
openGauss020-openGauss 向量数据库深度解析:从存储到AI的全栈优化
前端
Y***98512 小时前
【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元
前端·spring boot·后端