学习目标
- 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基础:理解三种状态
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实战:博客数据获取
并行执行:别傻等,一起做
上面的代码有个问题——获取文章和获取作者是串行的,总共需要 500ms + 300ms = 800ms。
但这两个请求其实互不依赖,可以同时发起,只需要 500ms(取最慢的那个)。
类比一下:
串行就像你做饭时——先洗菜(5分钟),再切菜(3分钟),最后炒菜(10分钟),总共18分钟。 并行就像你同时用两个锅——一个炒菜,一个煮汤,总共只需要10分钟。
并行执行实战
常见错误
常见错误
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 | 处理异步错误 | 出问题知道怎么办 |
对比一下:
| 学习前 | 学习后 | |-------|-------| | 数据写死在代码里 | 可以从服务器获取数据 | | 只能串行执行 | 可以并行执行提高效率 | | 错误处理不完善 | 完善的错误处理机制 |
下一章预告:
在下一章中,我们将学习装饰器——一个强大的特性,可以让你优雅地扩展类和方法的功能。日志记录、权限验证、性能监控,都可以用装饰器实现。
就像学会了开车和认路,现在要学怎么给车装涡轮增压了!
交互式练习
异步编程练习:博客数据加载
使用 async/await 实现博客文章加载功能
章节测验
async 函数总是返回什么?