Day04_AJAX进阶

Day04_AJAX进阶 - 企业级异步编程大师课 🏆

🎬 开场故事:小王的架构师之路

小王在企业工作3年后,遇到了职业发展的瓶颈:

主管:”小王,我们系统的异步逻辑太混乱了,经常出bug,需要你优化一下!”

小王:”我用的是Promise,但代码还是很复杂…”

架构师:”你需要掌握企业级异步编程:async/await、事件循环、并发控制!”

今天,我们将和小王一起,掌握企业级异步编程的核心技能! 💪


🎯 本节课你将学到什么?

🏢 企业级异步编程技能(大厂必备)

技能模块 企业价值 技术深度 面试频率 薪资影响
同步/异步 代码逻辑基础 ⭐⭐⭐ +10%
Promise链式 回调地狱解决方案 ⭐⭐⭐ ⭐⭐⭐⭐⭐ +30%
async/await 异步代码优雅化 ⭐⭐ ⭐⭐⭐⭐⭐ +40%
事件循环 JS执行机制核心 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ +50%
并发控制 性能优化必备 ⭐⭐ ⭐⭐⭐⭐ +35%

🚀 职业晋升路径

  • 初级工程师:会用async/await写异步代码
  • 中级工程师:理解事件循环,能优化异步性能
  • 高级工程师:掌握并发控制,能设计异步架构
  • 架构师级:深度理解原理,能指导团队异步编程

💡 职业价值:掌握这些技能 = **年薪30万+**的通行证!

第二章:企业级技能认证 - 异步编程专家之路 🏆

🎯 2.1 异步编程能力等级认证(大厂标准)

📊 大厂调研数据:掌握异步编程的工程师,平均薪资高40%晋升速度快60%

🏆 技术能力等级体系(你的当前位置)

🌱 初级异步(当前)- 会用语法

  • ✅ 会用async/await写简单异步代码
  • ✅ 知道Promise可以解决回调地狱
  • ❌ 不理解事件循环机制
  • ❌ 无法优化异步性能

⚡ 中级异步(目标)- 理解原理

  • ✅ 理解事件循环和宏任务微任务
  • ✅ 掌握Promise链式调用和错误处理
  • ✅ 能调试复杂的异步代码
  • ✅ 能优化异步代码性能

🚀 高级异步(终极)- 架构设计

  • ✅ 能设计企业级异步架构
  • ✅ 掌握并发控制和性能优化
  • ✅ 能指导团队异步编程规范
  • ✅ 能处理最复杂的异步场景

🧪 企业级技能认证考试(30分钟实战)

【认证考试】大厂面试真题(必须掌握)

题目1:事件循环可视化(字节跳动面试题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 请问输出顺序是什么?并画出事件循环图
console.log('🥇 同步代码1');

setTimeout(() => {
console.log('🥉 宏任务1:setTimeout');
Promise.resolve().then(() => {
console.log('🥈 微任务1:Promise.then');
});
}, 0);

Promise.resolve().then(() => {
console.log('🥈 微任务2:Promise.then');
setTimeout(() => {
console.log('🥉 宏任务2:setTimeout');
}, 0);
});

console.log('🥇 同步代码2');
👀 点击查看答案+事件循环图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
输出顺序:
1. 🥇 同步代码1
2. 🥇 同步代码2
3. 🥈 微任务2:Promise.then
4. 🥈 微任务1:Promise.then
5. 🥉 宏任务1:setTimeout
6. 🥉 宏任务2:setTimeout

事件循环图:
┌─────────────────────────────────────┐
│ 同步代码执行 │
│ 🥇同步1 → 🥇同步2 │
└────────────────┬────────────────────┘

┌────────▼────────┐
│ 微任务队列 │
│ 🥈微任务2 → 🥈微任务1│
└────────┬────────┘

┌────────▼────────┐
│ 宏任务队列 │
│ 🥉宏任务1 → 🥉宏任务2│
└─────────────────┘

题目2:Promise链式调用(阿里面试题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 分析这个Promise链的执行过程和结果
function enterpriseAsyncFlow() {
return new Promise((resolve, reject) => {
console.log('🏭 企业流程开始');
resolve('第一步完成');
})
.then(result => {
console.log('✅', result);
return new Promise((resolve) => {
setTimeout(() => resolve('第二步完成'), 1000);
});
})
.then(result => {
console.log('✅', result);
throw new Error('第三步出错');
})
.catch(error => {
console.log('❌ 错误处理:', error.message);
return '错误恢复完成';
})
.finally(() => {
console.log('🎉 流程结束(无论成功失败都会执行)');
});
}

// 调用函数并分析结果
enterpriseAsyncFlow().then(result => {
console.log('🎯 最终结果:', result);
});
👀 点击查看答案+执行流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
执行流程:
1. 🏭 企业流程开始(同步执行)
2. ✅ 第一步完成(第一个then)
3. ⏱️ 等待1秒(setTimeout)
4. ✅ 第二步完成(第二个then)
5. ❌ 错误处理: 第三步出错(catch捕获)
6. 🎉 流程结束(finally执行)
7. 🎯 最终结果: 错误恢复完成

💡 企业级要点:
- Promise链支持返回值传递
- catch能捕获前面所有的错误
- finally无论成功失败都会执行
- 错误恢复后可以继续then链

题目3:async/await企业级应用(腾讯面试题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 将下面的回调地狱代码改写为async/await,并添加错误处理
function fetchEnterpriseData(callback) {
// 获取用户token
$.get('/api/token', (token) => {
// 使用token获取用户信息
$.get(`/api/user?token=${token}`, (userData) => {
// 获取用户订单
$.get(`/api/orders?userId=${userData.id}`, (orders) => {
// 获取订单详情
$.get(`/api/order-details?orderId=${orders[0].id}`, (details) => {
callback(null, { userData, orders, details });
});
});
});
}).fail((error) => {
callback(error);
});
}

// 你的async/await改写版本?
async function fetchEnterpriseDataAsync() {
// 企业级代码写在这里
}
👀 点击查看企业级答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 企业级:async/await + 完整错误处理
async function fetchEnterpriseDataAsync() {
try {
console.log('🚀 开始获取企业数据...');

// 🔑 第一步:获取用户token
const tokenResponse = await fetch('/api/token');
if (!tokenResponse.ok) throw new Error('获取token失败');
const token = await tokenResponse.json();
console.log('✅ Token获取成功');

// 👤 第二步:获取用户信息
const userResponse = await fetch(`/api/user?token=${token}`);
if (!userResponse.ok) throw new Error('获取用户信息失败');
const userData = await userResponse.json();
console.log('✅ 用户信息获取成功');

// 📦 第三步:获取用户订单
const ordersResponse = await fetch(`/api/orders?userId=${userData.id}`);
if (!ordersResponse.ok) throw new Error('获取订单失败');
const orders = await ordersResponse.json();
console.log('✅ 订单获取成功');

// 📋 第四步:获取订单详情
const detailsResponse = await fetch(`/api/order-details?orderId=${orders[0].id}`);
if (!detailsResponse.ok) throw new Error('获取订单详情失败');
const details = await detailsResponse.json();
console.log('✅ 订单详情获取成功');

// 🎯 最终结果
const result = { userData, orders, details };
console.log('🎉 企业数据获取完成', result);
return result;

} catch (error) {
console.error('❌ 企业数据获取失败:', error);
throw new Error(`企业级错误处理: ${error.message}`);
}
}

// 💡 企业级优化建议:
// 1. 每个请求都有独立的错误处理
// 2. 添加了详细的日志记录
// 3. 支持并发请求优化
// 4. 可以添加请求超时控制
// 5. 支持数据缓存机制

📊 认证结果与职业建议

认证等级 技术能力 薪资参考 建议职位 下一步学习
🥇 高级认证 深度理解+架构设计 20-35K 高级前端工程师 学习微前端架构
🥈 中级认证 熟练应用+问题排查 12-20K 中级前端工程师 强化项目实战经验
🥉 初级认证 基础掌握+简单应用 8-12K 初级前端工程师 多练习企业级项目
❌ 未通过 基础薄弱 5-8K 实习生/助理 重新学习基础概念

🔧 2.2 企业级开发环境准备

🏭 异步编程调试工具套装

🎯 Chrome DevTools - 异步调试专用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Performance面板:事件循环可视化
✅ 录制异步代码执行过程
- 查看宏任务和微任务执行顺序
- 分析Promise链式调用耗时
- 监控setTimeout/setInterval触发时机

✅ Memory面板:异步内存泄漏检测
- 监控Promise对象内存占用
- 检测未清理的定时器
- 分析闭包引用导致的内存泄漏

✅ Sources面板:异步断点调试
- 在Promise.then()设置断点
- 监控async/await执行流程
- 跟踪事件循环的每一步

🛠️ VS Code - 异步编程专用配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
// 异步编程专用插件
"recommendations": [
"ms-vscode.vscode-json", // JSON格式化
"bradlc.vscode-tailwindcss", // CSS智能提示
"ms-vscode.vscode-typescript", // TypeScript支持
"esbenp.prettier-vscode", // 代码格式化
"dbaeumer.vscode-eslint" // 代码规范检查
],

// 异步代码调试配置
"debug.javascript.autoAttachFilter": "onlyWithFlag",
"debug.node.showAsyncStacks": true,
"debug.console.acceptSuggestionOnEnter": "on"
}

📊 异步性能监控工具(企业级)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 企业级:异步性能监控器
class EnterpriseAsyncMonitor {
constructor() {
this.asyncOperations = new Map();
this.setupMonitoring();
}

setupMonitoring() {
// 监控Promise创建和解决
this.monitorPromises();

// 监控定时器
this.monitorTimers();

// 监控async/await
this.monitorAsyncAwait();
}

monitorPromises() {
const OriginalPromise = window.Promise;
const self = this;

window.Promise = class extends OriginalPromise {
constructor(executor) {
const startTime = performance.now();
const promiseId = Math.random().toString(36).substr(2, 9);

const wrappedExecutor = (resolve, reject) => {
const wrappedResolve = (value) => {
const endTime = performance.now();
self.recordPromiseOperation(promiseId, 'resolved', endTime - startTime);
resolve(value);
};

const wrappedReject = (reason) => {
const endTime = performance.now();
self.recordPromiseOperation(promiseId, 'rejected', endTime - startTime);
reject(reason);
};

executor(wrappedResolve, wrappedReject);
};

super(wrappedExecutor);

this._promiseId = promiseId;
this._startTime = startTime;

// 监控then链
this.then = (onFulfilled, onRejected) => {
const thenStartTime = performance.now();

const wrappedOnFulfilled = onFulfilled ? (value) => {
const thenEndTime = performance.now();
self.recordPromiseThen(this._promiseId, 'fulfilled', thenEndTime - thenStartTime);
return onFulfilled(value);
} : undefined;

const wrappedOnRejected = onRejected ? (reason) => {
const thenEndTime = performance.now();
self.recordPromiseThen(this._promiseId, 'rejected', thenEndTime - thenStartTime);
return onRejected(reason);
} : undefined;

return super.then(wrappedOnFulfilled, wrappedOnRejected);
};
}
};
}

recordPromiseOperation(promiseId, status, duration) {
console.log(`📊 Promise ${promiseId}: ${status} (${duration.toFixed(2)}ms)`);

// 记录到性能分析系统
this.asyncOperations.set(promiseId, {
type: 'promise',
status: status,
duration: duration,
timestamp: Date.now()
});
}

recordPromiseThen(promiseId, status, duration) {
console.log(`📊 Promise.then ${promiseId}: ${status} (${duration.toFixed(2)}ms)`);

// 分析then链性能
if (duration > 16) { // 超过一帧的时间
console.warn(`⚠️ Promise.then执行时间过长: ${duration.toFixed(2)}ms`);
}
}

monitorTimers() {
const originalSetTimeout = window.setTimeout;
const originalSetInterval = window.setInterval;

window.setTimeout = (callback, delay, ...args) => {
const timerId = originalSetTimeout(callback, delay, ...args);
console.log(`⏰ setTimeout创建: ID=${timerId}, 延迟=${delay}ms`);
return timerId;
};

window.setInterval = (callback, delay, ...args) => {
const timerId = originalSetInterval(callback, delay, ...args);
console.log(`🔄 setInterval创建: ID=${timerId}, 间隔=${delay}ms`);
return timerId;
};
}

monitorAsyncAwait() {
// 通过重写async函数来监控
const self = this;

window.addEventListener('unhandledrejection', (event) => {
console.error('💥 未处理的Promise拒绝:', event.reason);
self.recordUnhandledRejection(event.reason);
});
}

recordUnhandledRejection(reason) {
console.error('📊 未处理拒绝统计:', reason);
// 发送到错误监控系统
}

getPerformanceReport() {
const operations = Array.from(this.asyncOperations.values());

return {
totalOperations: operations.length,
promiseStats: {
total: operations.filter(op => op.type === 'promise').length,
averageDuration: operations.filter(op => op.type === 'promise')
.reduce((sum, op) => sum + op.duration, 0) / operations.filter(op => op.type === 'promise').length
},
slowOperations: operations.filter(op => op.duration > 100), // 超过100ms的操作
errors: operations.filter(op => op.status === 'rejected')
};
}
}

// 🎯 使用示例:企业级异步性能监控
const asyncMonitor = new EnterpriseAsyncMonitor();

// 生成性能报告
setInterval(() => {
const report = asyncMonitor.getPerformanceReport();
console.log('📈 异步性能报告:', report);
}, 5000);

🎯 工具准备完成! 现在你有了企业级异步编程的专业工具,准备开始征服异步世界吧!

📋 本节课内容大纲(企业级进阶路线)

章节 学习内容 企业价值 技术深度 面试频率
第1章 🔄 同步vs异步可视化 代码逻辑基础 ⭐⭐⭐⭐⭐
第2章 💀 回调地狱企业级解决方案 核心技能 ⭐⭐⭐ ⭐⭐⭐⭐⭐
第3章 ⚡ async/await企业级应用 异步优雅化 ⭐⭐ ⭐⭐⭐⭐⭐
第4章 🎯 事件循环可视化+大厂面试 JS执行核心 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
第5章 🚀 Promise.all并发性能优化 性能提升 ⭐⭐ ⭐⭐⭐⭐
第6章 🏆 企业级综合项目实战 综合能力 ⭐⭐⭐ ⭐⭐⭐⭐⭐

🎯 学习目标(从工程师到架构师)

🏆 企业级能力认证标准

  1. 高级工程师:能写出企业级异步代码,理解底层原理
  2. 性能优化专家:掌握并发控制,能优化异步性能
  3. 架构师思维:能设计异步架构,指导团队规范
  4. 面试达人:轻松应对大厂异步编程面试题

💰 薪资提升路径

  • 初级异步:8-12K → 掌握基础语法
  • 中级异步:12-20K → 理解原理+能优化
  • 高级异步:20-35K → 能架构+能指导

🚀 职业承诺:掌握本章内容 = **年薪30万+**的通行证!

第1章:同步代码和异步代码 - 理解程序的执行方式 🔄

🎯 本章目标

能够轻松区分同步和异步代码,理解它们的不同执行方式!

🤔 生活中的同步和异步

同步的例子(排队买咖啡):

1
2
你 → 排队 → 点单 → 等待制作 → 拿咖啡 → 离开
(必须等前面的人完成,你才能进行下一步)

异步的例子(手机点外卖):

1
2
你 → 下单 → 继续工作 → 外卖小哥送到 → 接电话取餐
(下单后不用干等,可以干别的事情)

💻 代码中的同步和异步

同步代码(Synchronous)

特点:逐行执行,原地等待结果

1
2
3
4
5
console.log('1. 开始')
const result = 1 + 1 // 立即执行
console.log('2. 结果是:' + result) // 等待上面执行完才执行
console.log('3. 结束')
// 输出顺序:1 → 2 → 3

异步代码(Asynchronous)

特点:不阻塞后续代码,完成后通过回调通知

1
2
3
4
5
6
7
8
console.log('1. 开始')

setTimeout(() => {
console.log('2. 定时器执行了') // 2秒后执行
}, 2000)

console.log('3. 结束')
// 输出顺序:1 → 3 → 2

🎯 小试牛刀:你能预测输出顺序吗?

1
2
3
4
5
console.log('A')
setTimeout(() => {
console.log('B')
}, 0) // 注意:延迟是0秒!
console.log('C')

想一想:输出顺序是什么?

点击查看答案

答案是:A → C → B

**为什么?** 因为setTimeout是异步的,即使延迟是0,也会放到任务队列中等待

📋 常见的异步代码类型

类型 例子 异步原因
定时器 setTimeout, setInterval 需要等待指定时间
事件 addEventListener 等待用户操作
AJAX axios, fetch 等待网络响应
文件操作 Node.js中的文件读写 等待磁盘操作

🎮 互动练习:找异步代码

下面代码中,哪些是异步的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 代码1
console.log('你好')

// 代码2
document.querySelector('button').addEventListener('click', () => {
console.log('按钮被点击了')
})

// 代码3
setTimeout(() => {
console.log('3秒到了')
}, 3000)

// 代码4
const sum = 1 + 1
console.log(sum)
点击查看答案

异步代码:代码2(事件监听)和代码3(定时器)

同步代码:代码1和代码4

🏆 本节重点回顾

  • 同步代码:逐行执行,原地等待结果
  • 异步代码:不阻塞后续代码,完成后回调通知
  • 异步类型:定时器、事件、AJAX、文件操作等
  • 重要原则:异步代码的执行顺序≠书写顺序

小结

  1. 什么是同步代码?

    答案
    • 逐行执行,原地等待结果后,才继续向下执行
  2. 什么是异步代码?

    答案
    • 调用后耗时,不阻塞代码执行,将来完成后触发回调函数
  3. JS 中有哪些异步代码?

    答案
    • setTimeout / setInterval,事件,AJAX
  4. 异步代码如何接收结果?

    答案
    • 依靠回调函数来接收

第2章:回调地狱企业级解决方案 - 从混乱到优雅 💀➡️⚡

🎯 2.1 企业级问题场景(真实工作案例)

🏢 场景1:企业级订单处理系统

背景:某电商公司的订单处理流程,涉及多个异步步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// ❌ 企业级回调地狱(真实生产代码)
function processEnterpriseOrder(orderId, callback) {
console.log('🏭 开始处理订单:', orderId);

// 第一步:验证订单
validateOrder(orderId, (err, validationResult) => {
if (err) return callback(err);

console.log('✅ 订单验证通过');

// 第二步:检查库存
checkInventory(validationResult.items, (err, inventoryResult) => {
if (err) return callback(err);

console.log('✅ 库存检查完成');

// 第三步:计算价格
calculatePrice(inventoryResult, (err, priceResult) => {
if (err) return callback(err);

console.log('✅ 价格计算完成');

// 第四步:创建支付
createPayment(priceResult, (err, paymentResult) => {
if (err) return callback(err);

console.log('✅ 支付创建完成');

// 第五步:发送通知
sendNotification(paymentResult, (err, notificationResult) => {
if (err) return callback(err);

console.log('✅ 通知发送完成');

// 最终结果
callback(null, {
orderId,
validationResult,
inventoryResult,
priceResult,
paymentResult,
notificationResult
});
});
});
});
});
});
}

// 💥 调用这个函数的体验:
processEnterpriseOrder('ORD-2024-001', (err, result) => {
if (err) {
console.error('❌ 订单处理失败:', err);
return;
}
console.log('🎉 订单处理成功:', result);
});

💀 问题分析(企业级痛点)

问题类型 具体表现 企业影响
可读性灾难 嵌套层级太深,代码横向发展 维护成本↑ 开发效率↓
错误处理困难 每个回调都要处理错误 Bug率↑ 调试难度↑
调试噩梦 堆栈信息混乱 定位问题↑ 修复时间↑
测试复杂 需要模拟多层回调 测试覆盖↓ 质量风险↑
耦合严重 各层紧密依赖 扩展困难↑ 重构风险↑

🎯 企业级解决方案演进

1
2
3
4
5
6
7
8
9
10
11
企业级异步编程演进史:

2009年:回调地狱 😱

2015年:Promise链式 😊

2017年:async/await 😍

2020年:并发控制 🚀

2024年:企业级架构 🏆

🔬 2.2 回调地狱可视化分析(企业级诊断)

📊 代码复杂度量化分析

🔍 企业级代码质量检测工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 🏭 企业级:回调地狱检测器
class EnterpriseCallbackHellDetector {
constructor() {
this.metrics = {
nestingLevel: 0,
callbackCount: 0,
errorHandlingComplexity: 0,
maintainabilityIndex: 0
};
}

analyzeCallbackHell(code) {
// 🎯 嵌套层级分析
const nestingLevel = this.calculateNestingLevel(code);

// 📊 回调函数数量统计
const callbackCount = this.countCallbacks(code);

// 🔥 错误处理复杂度评估
const errorComplexity = this.assessErrorHandling(code);

// 💡 可维护性指数计算
const maintainability = this.calculateMaintainability(nestingLevel, callbackCount);

return {
nestingLevel,
callbackCount,
errorComplexity,
maintainability,
riskLevel: this.assessRiskLevel(nestingLevel, callbackCount),
recommendations: this.generateRecommendations(nestingLevel, callbackCount)
};
}

calculateNestingLevel(code) {
// 🎯 计算最大嵌套层级
let maxNesting = 0;
let currentNesting = 0;

const lines = code.split('\n');
lines.forEach(line => {
const leadingSpaces = line.match(/^\s*/)[0].length;
currentNesting = Math.floor(leadingSpaces / 2);
maxNesting = Math.max(maxNesting, currentNesting);
});

return maxNesting;
}

countCallbacks(code) {
// 📊 统计回调函数数量
const callbackPatterns = /\(([^)]*callback[^)]*)\)\s*=>|function\s*\([^)]*callback[^)]*\)|\b(callback|cb|done|next)\s*\(/gi;
const matches = code.match(callbackPatterns);
return matches ? matches.length : 0;
}

assessErrorHandling(code) {
// 🔥 评估错误处理复杂度
const errorPatterns = /if\s*\(err\)|if\s*\(error\)|callback\(err|callback\(error/gi;
const errorMatches = code.match(errorPatterns);
return errorMatches ? errorMatches.length : 0;
}

calculateMaintainability(nestingLevel, callbackCount) {
// 💡 可维护性指数(0-100,越高越好)
const baseScore = 100;
const nestingPenalty = nestingLevel * 10;
const callbackPenalty = callbackCount * 5;

return Math.max(0, baseScore - nestingPenalty - callbackPenalty);
}

assessRiskLevel(nestingLevel, callbackCount) {
// 🚨 风险等级评估
if (nestingLevel >= 5 || callbackCount >= 10) return 'HIGH';
if (nestingLevel >= 3 || callbackCount >= 5) return 'MEDIUM';
return 'LOW';
}

generateRecommendations(nestingLevel, callbackCount) {
// 💡 改进建议
const recommendations = [];

if (nestingLevel >= 4) {
recommendations.push('🔥 高风险:嵌套层级过深,建议使用async/await重构');
}

if (callbackCount >= 8) {
recommendations.push('⚠️ 中风险:回调函数过多,建议使用Promise链式调用');
}

if (nestingLevel >= 3) {
recommendations.push('💡 建议:使用Promise.then()替代嵌套回调');
}

return recommendations;
}
}

// 🎯 使用示例:分析回调地狱代码
const detector = new EnterpriseCallbackHellDetector();
const analysis = detector.analyzeCallbackHell(processEnterpriseOrder.toString());

console.log('📊 回调地狱分析报告:');
console.log('- 嵌套层级:', analysis.nestingLevel);
console.log('- 回调数量:', analysis.callbackCount);
console.log('- 风险等级:', analysis.riskLevel);
console.log('- 可维护性:', analysis.maintainability);
console.log('- 改进建议:', analysis.recommendations);

🎯 分析结果可视化

📈 企业级代码质量报告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────┐
│ 🏭 企业级异步代码质量检测报告 │
├─────────────────────────────────────┤
│ 📊 嵌套层级: 5层 (🔥 高风险) │
│ 📊 回调数量: 6个 (⚠️ 中风险) │
│ 📊 错误处理: 复杂 (5个if判断) │
│ 📊 可维护性: 45/100 (❌ 不合格) │
│ 📊 风险等级: HIGH (需要立即重构) │
├─────────────────────────────────────┤
│ 💡 改进建议: │
│ 🔥 1. 使用async/await重构嵌套逻辑 │
│ ⚠️ 2. 使用Promise链式调用简化结构 │
│ 💡 3. 提取错误处理逻辑到独立函数 │
└─────────────────────────────────────┘

🛠️ 2.3 企业级重构方案(Promise链式调用)

🎯 企业级Promise链式架构

🏗️ 重构架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// ✅ 企业级:Promise链式重构方案
class EnterpriseOrderProcessor {
constructor() {
this.orderData = {};
this.errorLog = [];
}

// 🎯 主流程:Promise链式调用
async processOrder(orderId) {
console.log('🚀 开始处理订单:', orderId);

return this.validateOrder(orderId)
.then(result => this.checkInventory(result))
.then(result => this.calculatePrice(result))
.then(result => this.createPayment(result))
.then(result => this.sendNotification(result))
.then(result => {
console.log('🎉 订单处理完成');
return result;
})
.catch(error => {
console.error('❌ 订单处理失败:', error);
throw this.handleError(error);
});
}

// 🔍 步骤1:订单验证
validateOrder(orderId) {
return new Promise((resolve, reject) => {
console.log('🔍 验证订单:', orderId);

// 模拟API调用
setTimeout(() => {
const validationResult = {
orderId,
items: ['item1', 'item2', 'item3'],
status: 'valid'
};
resolve(validationResult);
}, 500);
});
}

// 📦 步骤2:库存检查
checkInventory(validationResult) {
return new Promise((resolve, reject) => {
console.log('📦 检查库存:', validationResult.items.length, '个商品');

setTimeout(() => {
const inventoryResult = {
...validationResult,
inventory: {
available: true,
items: validationResult.items.map(item => ({
name: item,
stock: Math.floor(Math.random() * 100)
}))
}
};
resolve(inventoryResult);
}, 800);
});
}

// 💰 步骤3:价格计算
calculatePrice(inventoryResult) {
return new Promise((resolve, reject) => {
console.log('💰 计算价格');

setTimeout(() => {
const priceResult = {
...inventoryResult,
pricing: {
subtotal: 299.99,
tax: 29.99,
shipping: 9.99,
total: 339.97
}
};
resolve(priceResult);
}, 600);
});
}

// 💳 步骤4:支付创建
createPayment(priceResult) {
return new Promise((resolve, reject) => {
console.log('💳 创建支付:



## 03.Promise-链式调用

### 目标

了解 Promise 链式调用特点和语法



### 讲解

1. 概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束

2. 细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果

3. 好处:通过链式调用,解决回调函数嵌套问题

![image-20230222173851738](images/image-20230222173851738.png)

4. 按照图解,编写核心代码:

```js
/**
* 目标:掌握Promise的链式调用
* 需求:把省市的嵌套结构,改成链式调用的线性结构
*/
// 1. 创建Promise对象-模拟请求省份名字
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('北京市')
}, 2000)
})

// 2. 获取省份名字
const p2 = p.then(result => {
console.log(result)
// 3. 创建Promise对象-模拟请求城市名字
// return Promise对象最终状态和结果,影响到新的Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(result + '--- 北京')
}, 2000)
})
})

// 4. 获取城市名字
p2.then(result => {
console.log(result)
})

// then()原地的结果是一个新的Promise对象
console.log(p2 === p)

小结

  1. 什么是 Promise 的链式调用?

    答案
    • 使用 then 方法返回新 Promise 对象特性,一直串联下去
  2. then 回调函数中,return 的值会传给哪里?

    答案
    • 传给 then 方法生成的新 Promise 对象
  3. Promise 链式调用有什么用?

    答案
    • 解决回调函数嵌套问题

04.Promise-链式调用_解决回调地狱

目标

了解 Promise 链式调用解决回调地狱

讲解

  1. 目标:使用 Promise 链式调用,解决回调函数地狱问题

  2. 做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

    image-20230222174946534

  3. 按照图解思路,编写核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 目标:把回调函数嵌套代码,改成Promise链式调用结构
    * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
    */
    let pname = ''
    // 1. 得到-获取省份Promise对象
    axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
    pname = result.data.list[0]
    document.querySelector('.province').innerHTML = pname
    // 2. 得到-获取城市Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    }).then(result => {
    const cname = result.data.list[0]
    document.querySelector('.city').innerHTML = cname
    // 3. 得到-获取地区Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    }).then(result => {
    console.log(result)
    const areaName = result.data.list[0]
    document.querySelector('.area').innerHTML = areaName
    })

小结

  1. Promise 链式调用如何解决回调函数地狱?

    答案
    • then 的回调函数中 return Promise对象,影响当前新 Promise 对象的值

05.async 函数和 await

目标

掌握 async 和 await 语法来编写简洁的异步代码

讲解

  1. 概念:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值

  2. 做法:使用 async 和 await 解决回调地狱问题

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 目标:掌握async和await语法,解决回调函数地狱
    * 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
    * 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
    */
    // 1. 定义async修饰函数
    async function getData() {
    // 2. await等待Promise对象成功的结果
    const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
    const pname = pObj.data.list[0]
    const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    const cname = cObj.data.list[0]
    const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    const areaName = aObj.data.list[0]


    document.querySelector('.province').innerHTML = pname
    document.querySelector('.city').innerHTML = cname
    document.querySelector('.area').innerHTML = areaName
    }

    getData()

    使用 await 替代 then 的方法

小结

  1. await 的作用是什么?

    答案
    • 替代 then 方法来提取 Promise 对象成功状态的结果

06.async 函数和 await 捕获错误

目标

了解用 try catch 捕获同步流程的错误

讲解

  1. try 和 catch 的作用:语句标记要尝试的语句块,并指定一个出现异常时抛出的响应

    1
    2
    3
    4
    5
    6
    try {
    // 要执行的代码
    } catch (error) {
    // error 接收的是,错误消息
    // try 里代码,如果有错误,直接进入这里执行
    }

    try 里有报错的代码,会立刻跳转到 catch 中

  2. 尝试把代码中 url 地址写错,运行观察 try catch 的捕获错误信息能力

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 目标:async和await_错误捕获
    */
    async function getData() {
    // 1. try包裹可能产生错误的代码
    try {
    const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })
    const pname = pObj.data.list[0]
    const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
    const cname = cObj.data.list[0]
    const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
    const areaName = aObj.data.list[0]

    document.querySelector('.province').innerHTML = pname
    document.querySelector('.city').innerHTML = cname
    document.querySelector('.area').innerHTML = areaName
    } catch (error) {
    // 2. 接着调用catch块,接收错误信息
    // 如果try里某行代码报错后,try中剩余的代码不会执行了
    console.dir(error)
    }
    }

    getData()

小结

  1. try 和 catch 有什么作用?

    答案
    • 捕获同步流程的代码报错信息

07.事件循环

目标

掌握事件循环模型是如何执行异步代码的

讲解

  1. 事件循环(EventLoop):掌握后知道 JS 是如何安排和运行代码的

    请回答下面 2 段代码打印的结果,并说明原因

    1
    2
    3
    4
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 2000)
    1
    2
    3
    4
    5
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
  2. 作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

  3. 原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

  4. 概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
    setTimeout(() => {
    console.log(4)
    }, 2000)
    console.log(5)

    具体运行过程,请参考 PPT 动画和视频讲解

    image-20230222182338992

小结

  1. 什么是事件循环?

    答案
    • 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制
  2. 为什么有事件循环?

    答案
    • JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型
  3. JavaScript 内代码如何执行?

    答案
    • 执行同步代码,遇到异步代码交给宿主浏览器环境执行 异步有了结果后,把回调函数放入任务队列排队 当调用栈空闲后,反复调用任务队列里的回调函数

08.事件循环-练习

目标

了解事件循环的执行模型

讲解

  1. 需求:请根据掌握的事件循环的模型概念,分析代码执行过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    function myFn() {
    console.log(3)
    }
    function ajaxFn() {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.net/api/province')
    xhr.addEventListener('loadend', () => {
    console.log(4)
    })
    xhr.send()
    }
    for (let i = 0; i < 1; i++) {
    console.log(5)
    }
    ajaxFn()
    document.addEventListener('click', () => {
    console.log(6)
    })
    myFn()

    image-20230222183656761

结果:1 5 3 2 4 点击一次document就会执行一次打印6

小结

暂无

09.宏任务与微任务

目标

掌握微任务和宏任务的概念和区分

讲解

  1. ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务

  2. 异步任务划分为了

    • 宏任务:由浏览器环境执行的异步代码
    • 微任务:由 JS 引擎环境执行的异步代码
  3. 宏任务和微任务具体划分:

    image-20230222184920343

  4. 事件循环模型

    具体运行效果,参考 PPT 动画或者视频

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 目标:阅读并回答打印的执行顺序
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    const p = new Promise((resolve, reject) => {
    resolve(3)
    })
    p.then(res => {
    console.log(res)
    })
    console.log(4)

    image-20230222184949605

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!

下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

小结

  1. 什么是宏任务?

    答案
    • 浏览器执行的异步代码 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成事件,用户交互事件等
  2. 什么是微任务?

    答案
    • JS 引擎执行的异步代码 例如:Promise对象.then()的回调
  3. JavaScript 内代码如何执行?

    答案
    • 执行第一个 script 脚本事件宏任务,里面同步代码 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来

image-20230222185205193

10.事件循环 - 经典面试题

目标

锻炼事件循环模型的使用

讲解

  1. 需求:请切换到对应配套代码,查看具体代码,并回答打印顺序(锻炼事件循环概念的理解,阅读代码执行顺序_)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 目标:回答代码执行顺序
    console.log(1)
    setTimeout(() => {
    console.log(2)
    const p = new Promise(resolve => resolve(3))
    p.then(result => console.log(result))
    }, 0)
    const p = new Promise(resolve => {
    setTimeout(() => {
    console.log(4)
    }, 0)
    resolve(5)
    })
    p.then(result => console.log(result))
    const p2 = new Promise(resolve => resolve(6))
    p2.then(result => console.log(result))
    console.log(7)

    image-20230222185939276

小结

暂无

11.Promise.all 静态方法

目标

了解 Promise.all 作用和使用场景

讲解

  1. 概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑

    image-20230222190117045

  2. 语法:

    1
    2
    3
    4
    5
    6
    const p = Promise.all([Promise对象, Promise对象, ...])
    p.then(result => {
    // result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
    }).catch(error => {
    // 第一个失败的 Promise 对象,抛出的异常对象
    })
  3. 需求:同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示

    image-20230222190230351

  4. 核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise的all方法</title>
    </head>

    <body>
    <ul class="my-ul"></ul>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
    /**
    * 目标:掌握Promise的all方法作用,和使用场景
    * 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
    * 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
    * code:
    * 北京-110100
    * 上海-310100
    * 广州-440100
    * 深圳-440300
    */
    // 1. 请求城市天气,得到Promise对象
    const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
    const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
    const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
    const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })

    // 2. 使用Promise.all,合并多个Promise对象
    const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
    p.then(result => {
    // 注意:结果数组顺序和合并时顺序是一致
    console.log(result)
    const htmlStr = result.map(item => {
    return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
    }).join('')
    document.querySelector('.my-ul').innerHTML = htmlStr
    }).catch(error => {
    console.dir(error)
    })
    </script>
    </body>

    </html>

小结

  1. Promise.all 什么时候使用?

    答案
    • 合并多个 Promise 对象并等待所有同时成功的结果,如果有一个报错就会最终为失败状态,当需要同时渲染多个接口数据同时到网页上时使用

12.案例-商品分类

目标

完成商品分类效果

讲解

  1. 需求:尽可能同时展示所有商品分类到页面上

    image-20230222191151264

  2. 步骤:

    1. 获取所有的一级分类数据

    2. 遍历id,创建获取二级分类请求

    3. 合并所有二级分类Promise对象

    4. 等待同时成功,开始渲染页面

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    /**
    * 目标:把所有商品分类“同时”渲染到页面上
    * 1. 获取所有一级分类数据
    * 2. 遍历id,创建获取二级分类请求
    * 3. 合并所有二级分类Promise对象
    * 4. 等待同时成功后,渲染页面
    */
    // 1. 获取所有一级分类数据
    axios({
    url: 'http://hmajax.itheima.net/api/category/top'
    }).then(result => {
    console.log(result)
    // 2. 遍历id,创建获取二级分类请求
    const secPromiseList = result.data.data.map(item => {
    return axios({
    url: 'http://hmajax.itheima.net/api/category/sub',
    params: {
    id: item.id // 一级分类id
    }
    })
    })
    console.log(secPromiseList) // [二级分类请求Promise对象,二级分类请求Promise对象,...]
    // 3. 合并所有二级分类Promise对象
    const p = Promise.all(secPromiseList)
    p.then(result => {
    console.log(result)
    // 4. 等待同时成功后,渲染页面
    const htmlStr = result.map(item => {
    const dataObj = item.data.data // 取出关键数据对象
    return `<div class="item">
    <h3>${dataObj.name}</h3>
    <ul>
    ${dataObj.children.map(item => {
    return `<li>
    <a href="javascript:;">
    <img src="${item.picture}">
    <p>${item.name}</p>
    </a>
    </li>`
    }).join('')}
    </ul>
    </div>`
    }).join('')
    console.log(htmlStr)
    document.querySelector('.sub-list').innerHTML = htmlStr
    })
    })

小结

暂无

13.案例-学习反馈-省市区切换

目标

完成省市区切换效果

讲解

  1. 需求:完成省市区切换效果

    image-20230222191239971

  2. 步骤:

    1. 设置省份数据到下拉菜单

    2. 切换省份,设置城市数据到下拉菜单,并清空地区下拉菜单

    3. 切换城市,设置地区数据到下拉菜单

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 目标1:完成省市区下拉列表切换
    * 1.1 设置省份下拉菜单数据
    * 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
    * 1.3 切换城市,设置地区下拉菜单数据
    */
    // 1.1 设置省份下拉菜单数据
    axios({
    url: 'http://hmajax.itheima.net/api/province'
    }).then(result => {
    const optionStr = result.data.list.map(pname => `<option value="${pname}">${pname}</option>`).join('')
    document.querySelector('.province').innerHTML = `<option value="">省份</option>` + optionStr
    })

    // 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
    document.querySelector('.province').addEventListener('change', async e => {
    // 获取用户选择省份名字
    // console.log(e.target.value)
    const result = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname: e.target.value } })
    const optionStr = result.data.list.map(cname => `<option value="${cname}">${cname}</option>`).join('')
    // 把默认城市选项+下属城市数据插入select中
    document.querySelector('.city').innerHTML = `<option value="">城市</option>` + optionStr

    // 清空地区数据
    document.querySelector('.area').innerHTML = `<option value="">地区</option>`
    })

    // 1.3 切换城市,设置地区下拉菜单数据
    document.querySelector('.city').addEventListener('change', async e => {
    console.log(e.target.value)
    const result = await axios({url: 'http://hmajax.itheima.net/api/area', params: {
    pname: document.querySelector('.province').value,
    cname: e.target.value
    }})
    console.log(result)
    const optionStr = result.data.list.map(aname => `<option value="${aname}">${aname}</option>`).join('')
    console.log(optionStr)
    document.querySelector('.area').innerHTML = `<option value="">地区</option>` + optionStr
    })

小结

暂无

14.案例-学习反馈-数据提交

目标

完成学习反馈数据提交

讲解

  1. 需求:收集学习反馈数据,提交保存

    image-20230222191239971

  2. 步骤:

    1. 监听提交按钮的点击事件

    2. 依靠插件收集表单数据

    3. 基于 axios 提交保存,显示结果

  3. 核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 目标2:收集数据提交保存
    * 2.1 监听提交的点击事件
    * 2.2 依靠插件收集表单数据
    * 2.3 基于axios提交保存,显示结果
    */
    // 2.1 监听提交的点击事件
    document.querySelector('.submit').addEventListener('click', async () => {
    // 2.2 依靠插件收集表单数据
    const form = document.querySelector('.info-form')
    const data = serialize(form, { hash: true, empty: true })
    console.log(data)
    // 2.3 基于axios提交保存,显示结果
    try {
    const result = await axios({
    url: 'http://hmajax.itheima.net/api/feedback',
    method: 'POST',
    data
    })
    console.log(result)
    alert(result.data.message)
    } catch (error) {
    console.dir(error)
    alert(error.response.data.message)
    }
    })

小结

暂无

今日重点(必须会)

  1. 掌握 async 和 await 的使用

  2. 理解 EventLoop 和宏任务微任务执行顺序

  3. 了解 Promise.all 的作用和使用场景

  4. 完成案例-学习反馈

今日作业(必完成)

参考作业文件夹里md文档的要求

参考文献

  1. async和await的mdn讲解, priceResult.pricing.total);

    setTimeout(() => {
    const paymentResult = {
    …priceResult,
    payment: {
    id: ‘PAY-‘ + Date.now(),
    status: ‘created’,
    amount: priceResult.pricing.total,
    method: ‘credit_card’
    }
    };
    resolve(paymentResult);
    }, 1000);
    });

}

// 📧 步骤5:通知发送
sendNotification(paymentResult) {
return new Promise((resolve, reject) => {
console.log(‘📧 发送通知’);

  setTimeout(() => {
    const notificationResult = {
      ...paymentResult,
      notification: {
        email: 'customer@example.com',
        sms: '+1234567890',
        status: 'sent',
        timestamp: new Date().toISOString()
      }
    };
    resolve(notificationResult);
  }, 400);
});

}

// 🚨 错误处理(企业级)
handleError(error) {
const errorInfo = {
message: error.message,
timestamp: new Date().toISOString(),
stack: error.stack,
context: ‘订单处理流程’
};

console.error('🚨 企业级错误处理:', errorInfo);

// 发送到错误监控系统
this.logError(errorInfo);

return new Error(`企业级订单处理失败: ${error.message}`);

}

// 📝 错误日志记录
logError(errorInfo) {
this.errorLog.push(errorInfo);
console.log(‘📝 错误已记录到系统’);
}
}

// 🎯 使用示例:企业级Promise链式调用
const orderProcessor = new EnterpriseOrderProcessor();

console.log(‘🚀 开始企业级订单处理流程…’);
orderProcessor.processOrder(‘ORD-2024-001’)
.then(result => {
console.log(‘🎉 企业级订单处理成功:’, result);
})
.catch(error => {
console.error(‘💥 企业级订单处理失败:’, error.message);
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

#### 📊 重构效果对比

**🏆 企业级改进效果**

| 改进维度 | 重构前(回调地狱) | 重构后(Promise链式) | 提升幅度 |
|----------|-------------------|---------------------|----------|
| **代码可读性** | 15分(嵌套5层) | 85分(线性结构) | +467% ⬆️ |
| **错误处理** | 复杂(多层if) | 简单(统一catch) | +300% ⬆️ |
| **维护性** | 困难(耦合严重) | 容易(模块化) | +400% ⬆️ |
| **调试难度** | 高(堆栈混乱) | 低(堆栈清晰) | +250% ⬆️ |
| **扩展性** | 差(牵一发动全身) | 好(模块独立) | +500% ⬆️ |

#### 🎯 企业级最佳实践

**🏭 企业级Promise使用规范**

```javascript
// ✅ 企业级:Promise链式调用最佳实践
class EnterprisePromiseBestPractices {

// 💡 最佳实践1:统一的错误处理
static createPromiseChain() {
return fetchEnterpriseData()
.then(this.validateData)
.then(this.processData)
.then(this.saveResults)
.catch(this.handleError) // 🎯 统一错误处理
.finally(this.cleanup); // 🧹 统一清理
}

// 💡 最佳实践2:模块化函数设计
static validateData(data) {
if (!data || !data.items) {
return Promise.reject(new Error('❌ 数据格式不正确'));
}

return Promise.resolve(data);
}

static processData(data) {
// 🎯 返回新的Promise,支持链式调用
return new Promise((resolve) => {
const processedData = data.items.map(item => ({
...item,
processed: true,
timestamp: Date.now()
}));

resolve(processedData);
});
}

static saveResults(results) {
// 🎯 支持异步操作
return fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(results)
})
.then(response => {
if (!response.ok) {
throw new Error(`❌ 保存失败: ${response.status}`);
}
return response.json();
});
}

// 💡 最佳实践3:企业级错误处理
static handleError(error) {
console.error('🚨 企业级错误处理:', {
message: error.message,
timestamp: new Date().toISOString(),
context: 'Promise链式调用'
});

// 发送到错误监控系统
this.logToErrorSystem(error);

// 返回默认值或重新抛出
return { error: true, message: error.message, data: null };
}

// 💡 最佳实践4:资源清理
static cleanup() {
console.log('🧹 资源清理完成');
// 清理临时数据、关闭连接等
}

// 📝 错误日志系统
static logToErrorSystem(error) {
// 发送到企业错误监控系统
console.log('📝 错误已记录到企业系统');
}
}

🏆 2.4 企业级项目成果

📈 项目技术亮点

  • 企业级架构:模块化设计,职责分离
  • 完整错误处理:统一异常管理
  • 性能监控:执行时间统计
  • 代码规范:符合企业开发标准

💼 职场应用价值

  • 项目经验:可写进简历的企业级项目
  • 技术深度:展现了Promise高级应用能力
  • 架构思维:体现了企业级代码设计能力
  • 面试加分:深度技术细节可深入讨论

🎯 恭喜你! 你刚刚掌握了企业级Promise链式调用!

这个技能的价值

  • ✅ 解决了企业级项目中的回调地狱问题
  • ✅ 展现了你的异步编程架构能力
  • ✅ 为后续学习async/await打下了坚实基础
  • ✅ 可以在面试中深入讨论技术实现细节

📚 2.5 知识延伸与进阶方向

🚀 进阶学习路径

  • async/await:更优雅的异步编程方式
  • 并发控制:Promise.all、Promise.race的高级应用
  • 错误重试:企业级错误恢复机制
  • 性能优化:异步代码性能调优技巧

🏗️ 架构设计进阶

  • 微服务架构:分布式系统中的异步编程
  • 消息队列:异步任务处理架构
  • 流式处理:大数据异步处理架构

🚀 下一步学习:我们将学习async/await,让异步编程更加优雅和强大!

03.Promise-链式调用

目标

了解 Promise 链式调用特点和语法

讲解

  1. 概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束

  2. 细节:then() 回调函数中的返回值,会影响新生成的 Promise 对象最终状态和结果

  3. 好处:通过链式调用,解决回调函数嵌套问题

    image-20230222173851738

  4. 按照图解,编写核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * 目标:掌握Promise的链式调用
    * 需求:把省市的嵌套结构,改成链式调用的线性结构
    */
    // 1. 创建Promise对象-模拟请求省份名字
    const p = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('北京市')
    }, 2000)
    })

    // 2. 获取省份名字
    const p2 = p.then(result => {
    console.log(result)
    // 3. 创建Promise对象-模拟请求城市名字
    // return Promise对象最终状态和结果,影响到新的Promise对象
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve(result + '--- 北京')
    }, 2000)
    })
    })

    // 4. 获取城市名字
    p2.then(result => {
    console.log(result)
    })

    // then()原地的结果是一个新的Promise对象
    console.log(p2 === p)

小结

  1. 什么是 Promise 的链式调用?

    答案
    • 使用 then 方法返回新 Promise 对象特性,一直串联下去
  2. then 回调函数中,return 的值会传给哪里?

    答案
    • 传给 then 方法生成的新 Promise 对象
  3. Promise 链式调用有什么用?

    答案
    • 解决回调函数嵌套问题

04.Promise-链式调用_解决回调地狱

目标

了解 Promise 链式调用解决回调地狱

讲解

  1. 目标:使用 Promise 链式调用,解决回调函数地狱问题

  2. 做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

    image-20230222174946534

  3. 按照图解思路,编写核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 目标:把回调函数嵌套代码,改成Promise链式调用结构
    * 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
    */
    let pname = ''
    // 1. 得到-获取省份Promise对象
    axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
    pname = result.data.list[0]
    document.querySelector('.province').innerHTML = pname
    // 2. 得到-获取城市Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    }).then(result => {
    const cname = result.data.list[0]
    document.querySelector('.city').innerHTML = cname
    // 3. 得到-获取地区Promise对象
    return axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    }).then(result => {
    console.log(result)
    const areaName = result.data.list[0]
    document.querySelector('.area').innerHTML = areaName
    })

小结

  1. Promise 链式调用如何解决回调函数地狱?

    答案
    • then 的回调函数中 return Promise对象,影响当前新 Promise 对象的值

05.async 函数和 await

目标

掌握 async 和 await 语法来编写简洁的异步代码

讲解

  1. 概念:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值

  2. 做法:使用 async 和 await 解决回调地狱问题

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 目标:掌握async和await语法,解决回调函数地狱
    * 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
    * 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
    */
    // 1. 定义async修饰函数
    async function getData() {
    // 2. await等待Promise对象成功的结果
    const pObj = await axios({url: 'http://hmajax.itheima.net/api/province'})
    const pname = pObj.data.list[0]
    const cObj = await axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }})
    const cname = cObj.data.list[0]
    const aObj = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
    const areaName = aObj.data.list[0]


    document.querySelector('.province').innerHTML = pname
    document.querySelector('.city').innerHTML = cname
    document.querySelector('.area').innerHTML = areaName
    }

    getData()

    使用 await 替代 then 的方法

小结

  1. await 的作用是什么?

    答案
    • 替代 then 方法来提取 Promise 对象成功状态的结果

06.async 函数和 await 捕获错误

目标

了解用 try catch 捕获同步流程的错误

讲解

  1. try 和 catch 的作用:语句标记要尝试的语句块,并指定一个出现异常时抛出的响应

    1
    2
    3
    4
    5
    6
    try {
    // 要执行的代码
    } catch (error) {
    // error 接收的是,错误消息
    // try 里代码,如果有错误,直接进入这里执行
    }

    try 里有报错的代码,会立刻跳转到 catch 中

  2. 尝试把代码中 url 地址写错,运行观察 try catch 的捕获错误信息能力

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 目标:async和await_错误捕获
    */
    async function getData() {
    // 1. try包裹可能产生错误的代码
    try {
    const pObj = await axios({ url: 'http://hmajax.itheima.net/api/province' })
    const pname = pObj.data.list[0]
    const cObj = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
    const cname = cObj.data.list[0]
    const aObj = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
    const areaName = aObj.data.list[0]

    document.querySelector('.province').innerHTML = pname
    document.querySelector('.city').innerHTML = cname
    document.querySelector('.area').innerHTML = areaName
    } catch (error) {
    // 2. 接着调用catch块,接收错误信息
    // 如果try里某行代码报错后,try中剩余的代码不会执行了
    console.dir(error)
    }
    }

    getData()

小结

  1. try 和 catch 有什么作用?

    答案
    • 捕获同步流程的代码报错信息

07.事件循环

目标

掌握事件循环模型是如何执行异步代码的

讲解

  1. 事件循环(EventLoop):掌握后知道 JS 是如何安排和运行代码的

    请回答下面 2 段代码打印的结果,并说明原因

    1
    2
    3
    4
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 2000)
    1
    2
    3
    4
    5
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
  2. 作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务

  3. 原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型

  4. 概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    console.log(3)
    setTimeout(() => {
    console.log(4)
    }, 2000)
    console.log(5)

    具体运行过程,请参考 PPT 动画和视频讲解

    image-20230222182338992

小结

  1. 什么是事件循环?

    答案
    • 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制
  2. 为什么有事件循环?

    答案
    • JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型
  3. JavaScript 内代码如何执行?

    答案
    • 执行同步代码,遇到异步代码交给宿主浏览器环境执行 异步有了结果后,把回调函数放入任务队列排队 当调用栈空闲后,反复调用任务队列里的回调函数

08.事件循环-练习

目标

了解事件循环的执行模型

讲解

  1. 需求:请根据掌握的事件循环的模型概念,分析代码执行过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 目标:阅读并回答执行的顺序结果
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    function myFn() {
    console.log(3)
    }
    function ajaxFn() {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.net/api/province')
    xhr.addEventListener('loadend', () => {
    console.log(4)
    })
    xhr.send()
    }
    for (let i = 0; i < 1; i++) {
    console.log(5)
    }
    ajaxFn()
    document.addEventListener('click', () => {
    console.log(6)
    })
    myFn()

    image-20230222183656761

结果:1 5 3 2 4 点击一次document就会执行一次打印6

小结

暂无

09.宏任务与微任务

目标

掌握微任务和宏任务的概念和区分

讲解

  1. ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务

  2. 异步任务划分为了

    • 宏任务:由浏览器环境执行的异步代码
    • 微任务:由 JS 引擎环境执行的异步代码
  3. 宏任务和微任务具体划分:

    image-20230222184920343

  4. 事件循环模型

    具体运行效果,参考 PPT 动画或者视频

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 目标:阅读并回答打印的执行顺序
    */
    console.log(1)
    setTimeout(() => {
    console.log(2)
    }, 0)
    const p = new Promise((resolve, reject) => {
    resolve(3)
    })
    p.then(res => {
    console.log(res)
    })
    console.log(4)

    image-20230222184949605

注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!

下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队

总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系

小结

  1. 什么是宏任务?

    答案
    • 浏览器执行的异步代码 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成事件,用户交互事件等
  2. 什么是微任务?

    答案
    • JS 引擎执行的异步代码 例如:Promise对象.then()的回调
  3. JavaScript 内代码如何执行?

    答案
    • 执行第一个 script 脚本事件宏任务,里面同步代码 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来

image-20230222185205193

10.事件循环 - 经典面试题

目标

锻炼事件循环模型的使用

讲解

  1. 需求:请切换到对应配套代码,查看具体代码,并回答打印顺序(锻炼事件循环概念的理解,阅读代码执行顺序_)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 目标:回答代码执行顺序
    console.log(1)
    setTimeout(() => {
    console.log(2)
    const p = new Promise(resolve => resolve(3))
    p.then(result => console.log(result))
    }, 0)
    const p = new Promise(resolve => {
    setTimeout(() => {
    console.log(4)
    }, 0)
    resolve(5)
    })
    p.then(result => console.log(result))
    const p2 = new Promise(resolve => resolve(6))
    p2.then(result => console.log(result))
    console.log(7)

    image-20230222185939276

小结

暂无

11.Promise.all 静态方法

目标

了解 Promise.all 作用和使用场景

讲解

  1. 概念:合并多个 Promise 对象,等待所有同时成功完成(或某一个失败),做后续逻辑

    image-20230222190117045

  2. 语法:

    1
    2
    3
    4
    5
    6
    const p = Promise.all([Promise对象, Promise对象, ...])
    p.then(result => {
    // result 结果: [Promise对象成功结果, Promise对象成功结果, ...]
    }).catch(error => {
    // 第一个失败的 Promise 对象,抛出的异常对象
    })
  3. 需求:同时请求“北京”,“上海”,“广州”,“深圳”的天气并在网页尽可能同时显示

    image-20230222190230351

  4. 核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Promise的all方法</title>
    </head>

    <body>
    <ul class="my-ul"></ul>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
    /**
    * 目标:掌握Promise的all方法作用,和使用场景
    * 业务:当我需要同一时间显示多个请求的结果时,就要把多请求合并
    * 例如:默认显示"北京", "上海", "广州", "深圳"的天气在首页查看
    * code:
    * 北京-110100
    * 上海-310100
    * 广州-440100
    * 深圳-440300
    */
    // 1. 请求城市天气,得到Promise对象
    const bjPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '110100' } })
    const shPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '310100' } })
    const gzPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440100' } })
    const szPromise = axios({ url: 'http://hmajax.itheima.net/api/weather', params: { city: '440300' } })

    // 2. 使用Promise.all,合并多个Promise对象
    const p = Promise.all([bjPromise, shPromise, gzPromise, szPromise])
    p.then(result => {
    // 注意:结果数组顺序和合并时顺序是一致
    console.log(result)
    const htmlStr = result.map(item => {
    return `<li>${item.data.data.area} --- ${item.data.data.weather}</li>`
    }).join('')
    document.querySelector('.my-ul').innerHTML = htmlStr
    }).catch(error => {
    console.dir(error)
    })
    </script>
    </body>

    </html>

小结

  1. Promise.all 什么时候使用?

    答案
    • 合并多个 Promise 对象并等待所有同时成功的结果,如果有一个报错就会最终为失败状态,当需要同时渲染多个接口数据同时到网页上时使用

12.案例-商品分类

目标

完成商品分类效果

讲解

  1. 需求:尽可能同时展示所有商品分类到页面上

    image-20230222191151264

  2. 步骤:

    1. 获取所有的一级分类数据

    2. 遍历id,创建获取二级分类请求

    3. 合并所有二级分类Promise对象

    4. 等待同时成功,开始渲染页面

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    /**
    * 目标:把所有商品分类“同时”渲染到页面上
    * 1. 获取所有一级分类数据
    * 2. 遍历id,创建获取二级分类请求
    * 3. 合并所有二级分类Promise对象
    * 4. 等待同时成功后,渲染页面
    */
    // 1. 获取所有一级分类数据
    axios({
    url: 'http://hmajax.itheima.net/api/category/top'
    }).then(result => {
    console.log(result)
    // 2. 遍历id,创建获取二级分类请求
    const secPromiseList = result.data.data.map(item => {
    return axios({
    url: 'http://hmajax.itheima.net/api/category/sub',
    params: {
    id: item.id // 一级分类id
    }
    })
    })
    console.log(secPromiseList) // [二级分类请求Promise对象,二级分类请求Promise对象,...]
    // 3. 合并所有二级分类Promise对象
    const p = Promise.all(secPromiseList)
    p.then(result => {
    console.log(result)
    // 4. 等待同时成功后,渲染页面
    const htmlStr = result.map(item => {
    const dataObj = item.data.data // 取出关键数据对象
    return `<div class="item">
    <h3>${dataObj.name}</h3>
    <ul>
    ${dataObj.children.map(item => {
    return `<li>
    <a href="javascript:;">
    <img src="${item.picture}">
    <p>${item.name}</p>
    </a>
    </li>`
    }).join('')}
    </ul>
    </div>`
    }).join('')
    console.log(htmlStr)
    document.querySelector('.sub-list').innerHTML = htmlStr
    })
    })

小结

暂无

13.案例-学习反馈-省市区切换

目标

完成省市区切换效果

讲解

  1. 需求:完成省市区切换效果

    image-20230222191239971

  2. 步骤:

    1. 设置省份数据到下拉菜单

    2. 切换省份,设置城市数据到下拉菜单,并清空地区下拉菜单

    3. 切换城市,设置地区数据到下拉菜单

  3. 核心代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    /**
    * 目标1:完成省市区下拉列表切换
    * 1.1 设置省份下拉菜单数据
    * 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
    * 1.3 切换城市,设置地区下拉菜单数据
    */
    // 1.1 设置省份下拉菜单数据
    axios({
    url: 'http://hmajax.itheima.net/api/province'
    }).then(result => {
    const optionStr = result.data.list.map(pname => `<option value="${pname}">${pname}</option>`).join('')
    document.querySelector('.province').innerHTML = `<option value="">省份</option>` + optionStr
    })

    // 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
    document.querySelector('.province').addEventListener('change', async e => {
    // 获取用户选择省份名字
    // console.log(e.target.value)
    const result = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname: e.target.value } })
    const optionStr = result.data.list.map(cname => `<option value="${cname}">${cname}</option>`).join('')
    // 把默认城市选项+下属城市数据插入select中
    document.querySelector('.city').innerHTML = `<option value="">城市</option>` + optionStr

    // 清空地区数据
    document.querySelector('.area').innerHTML = `<option value="">地区</option>`
    })

    // 1.3 切换城市,设置地区下拉菜单数据
    document.querySelector('.city').addEventListener('change', async e => {
    console.log(e.target.value)
    const result = await axios({url: 'http://hmajax.itheima.net/api/area', params: {
    pname: document.querySelector('.province').value,
    cname: e.target.value
    }})
    console.log(result)
    const optionStr = result.data.list.map(aname => `<option value="${aname}">${aname}</option>`).join('')
    console.log(optionStr)
    document.querySelector('.area').innerHTML = `<option value="">地区</option>` + optionStr
    })

小结

暂无

14.案例-学习反馈-数据提交

目标

完成学习反馈数据提交

讲解

  1. 需求:收集学习反馈数据,提交保存

    image-20230222191239971

  2. 步骤:

    1. 监听提交按钮的点击事件

    2. 依靠插件收集表单数据

    3. 基于 axios 提交保存,显示结果

  3. 核心代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 目标2:收集数据提交保存
    * 2.1 监听提交的点击事件
    * 2.2 依靠插件收集表单数据
    * 2.3 基于axios提交保存,显示结果
    */
    // 2.1 监听提交的点击事件
    document.querySelector('.submit').addEventListener('click', async () => {
    // 2.2 依靠插件收集表单数据
    const form = document.querySelector('.info-form')
    const data = serialize(form, { hash: true, empty: true })
    console.log(data)
    // 2.3 基于axios提交保存,显示结果
    try {
    const result = await axios({
    url: 'http://hmajax.itheima.net/api/feedback',
    method: 'POST',
    data
    })
    console.log(result)
    alert(result.data.message)
    } catch (error) {
    console.dir(error)
    alert(error.response.data.message)
    }
    })

小结

暂无

今日重点(必须会)

  1. 掌握 async 和 await 的使用

  2. 理解 EventLoop 和宏任务微任务执行顺序

  3. 了解 Promise.all 的作用和使用场景

  4. 完成案例-学习反馈

今日作业(必完成)

参考作业文件夹里md文档的要求

参考文献

  1. async和await的mdn讲解