首页/10

10章:异步编程:让博客动起来

Promise和async/await——处理网络请求、数据库操作

45分钟
4个学习目标

学习目标

  • 1理解什么是异步编程,为什么需要它
  • 2掌握Promise的基本用法
  • 3学会使用async/await简化异步代码
  • 4掌握异步错误处理

上一章回顾:类型灵活了,但数据从哪来?

在上一章中,我们学会了用联合类型、交叉类型、类型守卫让类型定义更灵活。博客的类型系统现在很强大了。

但你可能发现了问题:

我们的博客数据都是写死在代码里的:

const posts: Post[] = [
  { id: 1, title: '第一篇', content: '内容' },
  { id: 2, title: '第二篇', content: '内容' },
];

真实的博客需要从服务器获取数据、保存到数据库、上传图片。这些操作都需要时间——你不可能瞬间从北京的服务器拿到数据。

这就是异步编程要解决的问题。

类比一下:

同步编程就像在餐厅点餐——你站在柜台前等,厨师做好了才给你。异步编程就像取号等餐——你先去坐着,餐好了叫号你再去取。在等餐的时候,你可以做其他事情(刷手机、聊天)。

| 同步 | 异步 | |-----|------| | 站着等 | 坐着等 | | 阻塞其他操作 | 不阻塞其他操作 | | 代码简单但效率低 | 代码复杂但效率高 |

为什么需要异步?

JavaScript是单线程的——同一时间只能做一件事。如果用同步方式做网络请求:

// 假设这是同步的(实际上不是)
const data = fetch('/api/posts'); // 需要2秒
console.log(data); // 必须等2秒后才能执行

// 这2秒内,整个页面都卡住了!
// 用户点击按钮没反应,滚动页面不动

异步操作的常见场景:

  • 网络请求(获取文章列表、保存评论)
  • 文件读写(上传图片)
  • 定时器(自动保存草稿)
  • 数据库查询(搜索文章)

Promise就是解决这个问题的方案。

类比一下:

Promise就像餐厅的取餐小票:

  • 小票代表"未来会拿到餐"的承诺
  • 餐做好了 = Promise完成(resolved)
  • 餐厅没食材了 = Promise失败(rejected)
  • 还在做 = Promise等待中(pending)

Promise基础:理解三种状态

typescript
加载中...

async/await:让异步代码看起来像同步

Promise的 .then() 链式调用虽然能用,但嵌套多了就像"回调地狱":

// 链式调用——不太好看
getPost(1)
  .then(post => getUser(post.authorId))
  .then(user => getPosts(user.id))
  .then(posts => console.log(posts))
  .catch(error => console.error(error));

async/await是Promise的语法糖,让异步代码看起来像同步代码:

// async/await——清晰明了
async function loadUserPosts() {
  try {
    const post = await getPost(1);
    const user = await getUser(post.authorId);
    const posts = await getPosts(user.id);
    console.log(posts);
  } catch (error) {
    console.error(error);
  }
}

类比一下:

Promise的 .then() 就像说"然后...然后...然后..."——能理解,但有点绕。 async/await 就像说"先做A,再做B,最后做C"——更自然,更符合人的思维。

async/await实战:博客数据获取

typescript
加载中...

并行执行:别傻等,一起做

上面的代码有个问题——获取文章和获取作者是串行的,总共需要 500ms + 300ms = 800ms。

但这两个请求其实互不依赖,可以同时发起,只需要 500ms(取最慢的那个)。

类比一下:

串行就像你做饭时——先洗菜(5分钟),再切菜(3分钟),最后炒菜(10分钟),总共18分钟。 并行就像你同时用两个锅——一个炒菜,一个煮汤,总共只需要10分钟。

并行执行实战

typescript
加载中...

常见错误

常见错误

1. 忘记处理错误

// 危险!如果请求失败,程序会崩溃
async function getPost(id: number) {
  const post = await fetchPost(id); // 可能抛出错误
  return post;
}

// 正确:用 try/catch 处理
async function getPost(id: number) {
  try {
    const post = await fetchPost(id);
    return post;
  } catch (error) {
    console.error('获取文章失败:', error);
    return null;
  }
}

2. 串行执行可以并行的请求

// 慢:串行执行
const user = await fetchUser(1);
const posts = await fetchPosts(1); // 不依赖 user,没必要等

// 快:并行执行
const [user, posts] = await Promise.all([
  fetchUser(1),
  fetchPosts(1),
]);

3. 在非async函数中使用await

// 错误
function getData() {
  return await fetch('/api/data'); // 语法错误!
}

// 正确
async function getData() {
  return await fetch('/api/data');
}

4. 忽略Promise的返回值

// 错误:没有 await,函数会立即返回 Promise 而不是结果
function getTitle() {
  const post = fetchPost(1); // 这是 Promise,不是 Post
  return post.title; // 错误!
}

// 正确
async function getTitle() {
  const post = await fetchPost(1);
  return post.title;
}

动手实践

现在轮到你了!让我们为博客实现异步数据获取。

练习1:模拟API调用

创建一个 fetchPosts 函数:

  • 使用 setTimeout 模拟网络延迟
  • 返回一个 Promise,resolve 一个文章数组
  • 支持按页码获取

练习2:实现错误重试

创建一个 fetchWithRetry 函数:

  • 接受一个异步函数和重试次数
  • 失败时自动重试
  • 超过重试次数后抛出错误

练习3:博客首页数据加载

实现一个 loadHomePage 函数:

  • 并行获取文章列表、用户信息、分类列表
  • 处理任何可能的错误
  • 返回所有数据

本章总结

恭喜你!你已经掌握了TypeScript的异步编程。

本章要点:

| 概念 | 作用 | 比喻 | |-----|------|------| | Promise | 代表未来的值 | 取餐小票 | | async/await | 让异步代码像同步 | 先做A再做B | | Promise.all | 并行执行多个请求 | 同时用两个锅 | | try/catch | 处理异步错误 | 出问题知道怎么办 |

对比一下:

| 学习前 | 学习后 | |-------|-------| | 数据写死在代码里 | 可以从服务器获取数据 | | 只能串行执行 | 可以并行执行提高效率 | | 错误处理不完善 | 完善的错误处理机制 |

下一章预告:

在下一章中,我们将学习装饰器——一个强大的特性,可以让你优雅地扩展类和方法的功能。日志记录、权限验证、性能监控,都可以用装饰器实现。

就像学会了开车和认路,现在要学怎么给车装涡轮增压了!

交互式练习

1 / 1

异步编程练习:博客数据加载

使用 async/await 实现博客文章加载功能

typescript
Loading...

章节测验

问题 1 / 3得分: 0 / 3

async 函数总是返回什么?

A
void
B
any
C
Promise
D
Observable