1.1 什么是异步
📖 学习路径建议:
先学 1.1(什么是异步),理解"同步"和"异步"的区别
先学 1.1(什么是异步),理解"同步"和"异步"的区别
同步(Synchronous):一件事做完再做下一件,排队执行。
异步(Asynchronous):发起一件事后不等它做完,先去做别的,做完了通知我。
生活中的例子:你在餐厅点了菜(发起请求),同步就是站在厨房门口等菜做好;异步就是回到座位玩手机,菜好了服务员端过来(回调通知)。
JavaScript 是单线程语言——同一时间只能做一件事。如果所有操作都是同步的,一个网络请求要等5秒,页面就会"卡住"5秒。异步解决了这个问题。
🧪 在线演示:点击运行查看同步与异步的区别
💡
setTimeout 不会卡住后面代码,等1秒后才执行回调。这就是异步。
1// 同步:代码从上到下依次执行
2console.log('1. 开始');
3console.log('2. 处理数据');
4console.log('3. 结束');
5// 输出:1 → 2 → 3(严格按顺序)
6
7// 异步:setTimeout 是异步的,不会卡住后面的代码
8console.log('1. 开始');
9setTimeout(function() {
10 console.log('2. 延迟执行');
11}, 1000);
12console.log('3. 结束');
13// 输出:1 → 3 → 2(3先执行,不等 setTimeout)
1.2 回调与回调地狱
📖 学习路径建议:
再学 1.2(回调与回调地狱),知道为什么要用 Promise
再学 1.2(回调与回调地狱),知道为什么要用 Promise
回调函数(Callback):把一个函数作为参数传给另一个函数,等任务完成后"回调"它。
这是异步编程最早的方式,简单场景没问题,但嵌套多了就变成"回调地狱"。
🧪 在线演示:回调地狱模拟
回调地狱的问题:
| 问题 | 说明 |
|---|---|
| 代码难读 | 嵌套5、6层,缩进成"金字塔" |
| 错误处理麻烦 | 每一层都要单独处理错误 |
| 难以维护 | 改一个地方可能影响所有嵌套层 |
1// 模拟异步获取博客数据(用 setTimeout 模拟网络请求)
2function getPosts(callback) {
3 setTimeout(() => {
4 let posts = [
5 { id: 1, title: 'JavaScript入门' },
6 { id: 2, title: 'CSS布局技巧' },
7 { id: 3, title: 'React教程' }
8 ];
9 callback(posts);
10 }, 500);
11}
12
13function getPostDetail(id, callback) {
14 setTimeout(() => {
15 let detail = { id: id, title: '文章' + id, content: '文章内容...' };
16 callback(detail);
17 }, 300);
18}
19
20function getComments(postId, callback) {
21 setTimeout(() => {
22 let comments = [
23 { user: '小明', text: '写得好!' },
24 { user: '小红', text: '收藏了' }
25 ];
26 callback(comments);
27 }, 200);
28}
29
30// 回调地狱:嵌套越多越难读
31getPosts(function(posts) {
32 console.log('获取文章列表:', posts);
33
34 getPostDetail(posts[0].id, function(detail) {
35 console.log('获取文章详情:', detail);
36
37 getComments(detail.id, function(comments) {
38 console.log('获取评论:', comments);
39 });
40 });
41});
1.3 Promise 基础
📖 学习路径建议:
然后学 1.3-1.4(Promise 基础和链式调用),掌握核心用法
然后学 1.3-1.4(Promise 基础和链式调用),掌握核心用法
Promise 就是一个"承诺"——它承诺"将来会给你一个结果",要么成功,要么失败。
Promise 的三种状态:
Promise 三种状态:
⏳ pending(进行中)— 菜正在做
✅ fulfilled(已成功)— 菜做好了
❌ rejected(已失败)— 菜做砸了
| 状态 | 含义 | 类比 |
|---|---|---|
pending(进行中) |
任务还没完成 | 菜正在做 |
fulfilled(已成功) |
任务成功完成 | 菜做好了 |
rejected(已失败) |
任务失败了 | 菜做砸了 |
基本语法:
基本语法示例:
let promise = new Promise(function(resolve, reject) {
// 做异步操作...
if (成功) {
resolve(结果); // 状态变为 fulfilled
} else {
reject(错误); // 状态变为 rejected
}
});
代码逐行讲解:
代码讲解表格:
| 代码 | 说明 |
|---|---|
new Promise(function(resolve, reject) {}) |
创建一个 Promise,参数是执行器函数 |
resolve(data) |
任务成功,把结果传出去 |
reject(error) |
任务失败,把错误传出去 |
.then(data => {}) |
成功时执行的回调,data 就是 resolve 传的值 |
.catch(error => {}) |
失败时执行的回调,error 就是 reject 传的值 |
🧪 在线演示:Promise 基础
1// 创建一个 Promise(模拟获取文章列表)
2function getPosts() {
3 return new Promise(function(resolve, reject) {
4 setTimeout(() => {
5 let success = true; // 模拟成功
6
7 if (success) {
8 // 成功:调用 resolve,传递数据
9 resolve([
10 { id: 1, title: 'JavaScript入门' },
11 { id: 2, title: 'CSS布局技巧' }
12 ]);
13 } else {
14 // 失败:调用 reject,传递错误信息
15 reject('网络请求失败');
16 }
17 }, 500);
18 });
19}
20
21// 使用 Promise:用 .then() 接收成功结果,用 .catch() 接收错误
22getPosts()
23 .then(function(data) {
24 console.log('获取成功:', data);
25 })
26 .catch(function(error) {
27 console.log('获取失败:', error);
28 });
1.4 Promise 链式调用
📖 学习路径建议:
然后学 1.3-1.4(Promise 基础和链式调用),掌握核心用法
然后学 1.3-1.4(Promise 基础和链式调用),掌握核心用法
Promise 的 .then() 返回一个新的 Promise,所以可以一直链式调用,彻底解决回调地狱。
🧪 在线演示:Promise 链式调用
回调地狱 vs Promise 链对比:
| 对比 | 回调地狱 | Promise 链 |
|---|---|---|
| 代码结构 | 嵌套的金字塔 | 扁平的链式调用 |
| 错误处理 | 每层单独写 | 一个 .catch() 捕获所有错误 |
| 可读性 | 差(越深越难读) | 好(从上往下读) |
1// 模拟获取博客数据的三个步骤
2function getPosts() {
3 return new Promise((resolve) => {
4 setTimeout(() => {
5 resolve([
6 { id: 1, title: 'JavaScript入门' },
7 { id: 2, title: 'CSS布局技巧' }
8 ]);
9 }, 500);
10 });
11}
12
13function getPostDetail(id) {
14 return new Promise((resolve) => {
15 setTimeout(() => {
16 resolve({ id, title: '文章' + id, content: '这是文章内容...' });
17 }, 300);
18 });
19}
20
21function getComments(postId) {
22 return new Promise((resolve) => {
23 setTimeout(() => {
24 resolve([
25 { user: '小明', text: '写得好!' },
26 { user: '小红', text: '收藏了' }
27 ]);
28 }, 200);
29 });
30}
31
32// 链式调用:比回调地狱清晰很多
33getPosts()
34 .then(posts => {
35 console.log('文章列表:', posts);
36 return getPostDetail(posts[0].id); // 返回下一个 Promise
37 })
38 .then(detail => {
39 console.log('文章详情:', detail);
40 return getComments(detail.id); // 返回下一个 Promise
41 })
42 .then(comments => {
43 console.log('评论列表:', comments);
44 })
45 .catch(error => {
46 // 任何一步出错都会到这里
47 console.log('出错了:', error);
48 });
1.5 async/await
📖 学习路径建议:
最后学 1.5-1.6(async/await 和错误处理),写出现代异步代码
最后学 1.5-1.6(async/await 和错误处理),写出现代异步代码
async/await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码,是目前最推荐的异步写法。
async:放在函数前面,声明这是一个异步函数await:放在 Promise 前面,等待 Promise 完成并拿到结果
基本语法:
async function 函数名() {
let 结果 = await 某个Promise;
// 使用结果...
}
🧪 在线演示:async/await
Promise 链 vs async/await 对比:
| 写法 | 代码风格 | 可读性 |
|---|---|---|
.then() 链 |
链式回调 | 较好 |
async/await |
像同步代码 | 最好 |
💡
await 的意思是"暂停这里,等这个 Promise 完成后再继续"。虽然代码看起来是"同步执行"的,但实际上不会卡住浏览器,因为 await 后面的代码会被放到微任务队列里。
1// 复用之前的模拟函数
2function getPosts() {
3 return new Promise((resolve) => {
4 setTimeout(() => {
5 resolve([
6 { id: 1, title: 'JavaScript入门' },
7 { id: 2, title: 'CSS布局技巧' }
8 ]);
9 }, 500);
10 });
11}
12
13function getPostDetail(id) {
14 return new Promise((resolve) => {
15 setTimeout(() => {
16 resolve({ id, title: '文章' + id, content: '这是文章内容...' });
17 }, 300);
18 });
19}
20
21function getComments(postId) {
22 return new Promise((resolve) => {
23 setTimeout(() => {
24 resolve([
25 { user: '小明', text: '写得好!' },
26 { user: '小红', text: '收藏了' }
27 ]);
28 }, 200);
29 });
30}
31
32// async/await 写法:代码从上往下读,像同步一样
33async function loadBlogData() {
34 let posts = await getPosts();
35 console.log('文章列表:', posts);
36
37 let detail = await getPostDetail(posts[0].id);
38 console.log('文章详情:', detail);
39
40 let comments = await getComments(detail.id);
41 console.log('评论列表:', comments);
42
43 return { posts, detail, comments };
44}
45
46// 调用 async 函数
47loadBlogData().then(result => {
48 console.log('所有数据加载完成:', result);
49});
1.6 错误处理
📖 学习路径建议:
最后学 1.5-1.6(async/await 和错误处理),写出现代异步代码
最后学 1.5-1.6(async/await 和错误处理),写出现代异步代码
⚠️ 常见错误提示:
•
•
•
• 别忘了处理错误——不加
•
async 函数一定返回 Promise,即使你 return 一个普通值•
await 只能在 async 函数里用,不能在普通函数里写 await•
Promise 一旦状态确定就不能再改变——resolve 后再 resolve 无效• 别忘了处理错误——不加
try/catch 或 .catch(),Promise 的错误会被"吞掉"
异步代码一定要处理错误,否则出错了你也不知道。
方式一:try/catch(推荐,配合 async/await)
🧪 在线演示:错误处理(点击按钮查看效果)
三种错误处理方式:
错误处理方式对比:
| 方式 | 写法 | 适用场景 |
|---|---|---|
try/catch |
try { await... } catch(e) {} |
async/await(推荐) |
.catch() |
promise.then().catch() |
Promise 链 |
.then(成功, 失败) |
promise.then(ok, err) |
一次性的(不推荐) |
⚠️ 新手易错:
await 后面的 Promise 如果 reject 了,await 会抛出异常,就像同步代码的 throw 一样。所以必须用 try/catch 包裹。
⚠️ 新手易错:在
forEach 里用 await 没效果!forEach 不会等 await 完成。要等就用 for...of 循环。
forEach vs for...of 对比:
/* ❌ 错误:forEach 里的 await 无效 */
posts.forEach(async (post) => {
let detail = await getDetail(post.id); // 不会等
});
/* ✅ 正确:用 for...of */
for (let post of posts) {
let detail = await getDetail(post.id); // 会等
}
🧪 在线演示:实践作业——回调改 async/await
1// 模拟一个可能失败的请求
2function getUser(id) {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 if (id > 0) {
6 resolve({ id, name: '用户' + id });
7 } else {
8 reject('无效的用户ID');
9 }
10 }, 300);
11 });
12}
13
14// 方式1:async/await + try/catch(推荐)
15async function fetchUser() {
16 try {
17 let user = await getUser(1);
18 console.log('获取成功:', user);
19 } catch (error) {
20 console.log('获取失败:', error);
21 }
22}
23
24// 传入无效ID,触发错误
25async function fetchInvalidUser() {
26 try {
27 let user = await getUser(-1);
28 console.log('这行不会执行');
29 } catch (error) {
30 console.log('捕获到错误:' + error);
31 }
32}
33
34// 方式2:.then() + .catch()
35getUser(2)
36 .then(user => console.log('成功:', user))
37 .catch(error => console.log('失败:', error));
本章复盘——Promise 与异步编程核心要点
核心要点总结:
| 要点 | 说明 |
|---|---|
| 异步 vs 同步 | 同步排队等,异步先做别的,完成后通知 |
| 回调地狱 | 嵌套太深代码难读,用 Promise 解决 |
| Promise | new Promise((resolve, reject) => {}),三种状态 |
| .then() / .catch() | 链式处理成功和失败 |
| async/await | async 声明异步函数,await 等待 Promise 完成 |
| 错误处理 | try/catch 包裹 await(推荐),或 .catch() 链式捕获 |
| await 限制 | 只能在 async 函数里用,forEach 里无效 |