04 AJAX 进阶
知识点自测
看如下标签回答如下问题?
html<select> <option value="北京">北京市</option> <option value="南京">南京市</option> <option value="天津">天津市</option> </select>当选中第二个
option时,JS 中获取下拉菜单select标签的value属性的值是多少?答案
- 南京。当选中第二个
option时,JS 中获取下拉菜单select标签的value属性的值是"南京"。 - 这是因为
value属性的值是当前选中的option元素的value属性值。 - 在这个例子中,第二个
option的value属性是"南京"。
- 南京。当选中第二个
页面上看到的是北京,还是北京市?
答案
- 北京市。页面上显示的文本是由选中的
option元素的文本内容决定的。 - 在这个例子中,当选中第一个
option时,页面上显示的是北京市。所以,页面上看到的是北京市。
- 北京市。页面上显示的文本是由选中的
我给
select标签的value属性赋予 "南京" 会有什么效果?答案
- 什么效果都没有,没有没有一个
option选项的value能匹配。 - 给
select标签的value属性赋予 "南京" 不会产生直观的效果,因为value属性通常是由当前选中的option决定的。
- 什么效果都没有,没有没有一个
学习目标
- 区分异步代码,回调函数地狱问题和所有解决防范(
Promise链式调用) - 掌握
async和await使用 - 掌握
EventLoop的概念 - 了解
Promise.all静态方法作用 - 完成省市区切换效果
同步代码和异步代码
小结
什么是同步代码?
- 逐行执行,原地等待结果后,才继续向下执行
什么是异步代码?
- 调用后耗时,不阻塞代码执行,将来完成后触发回调函数
JS 中有哪些异步代码?
setTimeout/setInterval- 事件
- AJAX
异步代码如何接收结果?
- 依靠回调函数来接收
回调函数地狱
- 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
- 缺点:可读性差,异常无法捕获,耦合性严重,牵一发动全身
示例 - 回调函数地狱
- 需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中
axios({ url: 'http://hmajax.itheima.net/api/province' }).then((result) => {
const pname = result.data.list[0];
document.querySelector('.province').innerHTML = pname;
// 获取第一个省份默认下属的第一个城市名字
axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } }).then((result) => {
const cname = result.data.list[0];
document.querySelector('.city').innerHTML = cname;
// 获取第一个城市默认下属第一个地区名字
axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } }).then((result) => {
document.querySelector('.area').innerHTML = result.data.list[0];
});
});
});小结
什么是回调函数地狱?
- 在回调函数一直向下嵌套回调函数,形成回调函数地狱
回调函数地狱问题?
- 可读性差
- 异常捕获困难
- 耦合性严重
Promise 链式调用
- 概念:依靠
then()方法会返回一个新生成的Promise对象特性,继续串联下一环任务,直到结束 - 细节:
then()回调函数中的返回值,会影响新生成的Promise对象最终状态和结果 - 好处:通过链式调用,解决回调函数嵌套问题

/**
* 目标:掌握 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);解决回调地狱
- 目标:使用 Promise 链式调用,解决回调函数地狱问题
- 做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来

示例 - Promise 链式调用解决回调函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>33 Promise 链式调用解决回调地狱 获取地方列表</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body class="p-3">
<div class="container">
<h2>33 Promise 链式调用解决回调地狱 获取地方列表</h2>
<form class="form-group row my-5 " id="editForm">
<div class="mb-3 col">
<label for="province" class="form-label">省份</label>
<select class="form-select" id="province" name="province">
<option value="北京" selected>北京</option>
</select>
</div>
<div class="mb-3 col">
<label for="city" class="form-label">城市</label>
<select class="form-select" id="city" name="city">
<option value="北京市" selected>北京市</option>
</select>
</div>
<div class="mb-3 col">
<label for="area" class="form-label">地区</label>
<select class="form-select" id="area" name="area">
<option value="东城区" selected>东城区</option>
</select>
</div>
</form>
</div>
<!-- <script src="./index.js"></script> -->
<script src="./js/axios.min.js"></script>
<script>
/**
* 目标:把回调函数嵌套代码,改成 Promise 链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
const province = document.querySelector('#province');
const city = document.querySelector('#city');
const area = document.querySelector('#area');
axios
.get('http://hmajax.itheima.net/api/province')
.then((res) => {
console.log(res.data);
// province.innerHTML = res.data.list.map(item => `
// <option value="${item}">${item}</option>
// `).join('')
province.innerHTML = `<option value="${res.data.list[6]}">${res.data.list[6]}</option>`;
return axios.get(`http://hmajax.itheima.net/api/city?pname=${province.value}`);
})
.then((res) => {
console.log(res.data);
// city.innerHTML = res.data.list.map(item => `
// <option value="${item}">${item}</option>
// `).join('')
city.innerHTML = `<option value="${res.data.list[6]}">${res.data.list[6]}</option>`;
return axios.get(`http://hmajax.itheima.net/api/area?pname=${province.value}&cname=${city.value}`);
})
.then((res) => {
console.log(res.data);
area.innerHTML = res.data.list.map((item) => `<option value="${item}">${item}</option>`).join('');
});
</script>
</body>
</html>小结
什么是 Promise 的链式调用?
- 使用
then方法返回新 Promise 对象特性,一直串联下去
- 使用
then回调函数中,return 的值会传给哪里?- 传给
then方法生成的新 Promise 对象
- 传给
Promise 链式调用有什么用?
- 解决回调函数嵌套问题
Promise 链式调用如何解决回调函数地狱?
then的回调函数中返回 Promise 对象,影响当前新 Promise 对象的值
async 函数和 await 捕获错误
- 概念:在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
- 做法:使用 async 和 await 解决回调地狱问题
示例 - async 函数和 await 解决回调函数地狱
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>34 async 函数和 await 解决回调函数地狱 获取地方列表</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body class="p-3">
<div class="container">
<h2>34 async 函数和 await 解决回调函数地狱 获取地方列表</h2>
<form class="form-group row my-5 " id="editForm">
<div class="mb-3 col">
<label for="province" class="form-label">省份</label>
<select class="form-select" id="province" name="province">
<option value="北京" selected>北京</option>
</select>
</div>
<div class="mb-3 col">
<label for="city" class="form-label">城市</label>
<select class="form-select" id="city" name="city">
<option value="北京市" selected>北京市</option>
</select>
</div>
<div class="mb-3 col">
<label for="area" class="form-label">地区</label>
<select class="form-select" id="area" name="area">
<option value="东城区" selected>东城区</option>
</select>
</div>
</form>
</div>
<!-- <script src="./index.js"></script> -->
<script src="./js/axios.min.js"></script>
<script>
/**
* 目标:掌握 async 和 await 语法,解决回调函数地狱
* 概念:在 async 函数内,使用 await 关键字,获取 Promise 对象"成功状态"结果值
* 注意:await 必须用在 async 修饰的函数内(await 会阻止"异步函数内"代码继续执行,原地等待结果)
*/
const province = document.querySelector('#province');
const city = document.querySelector('#city');
const area = document.querySelector('#area');
// 1. 定义 async 修饰函数
async function getData() {
// 2. 在 async 函数内,使用 await 关键字,获取 Promise 对象"成功状态"结果值
const res1 = await axios.get('http://hmajax.itheima.net/api/province');
console.log(res1.data);
province.innerHTML = `<option value="${res1.data.list[10]}">${res1.data.list[10]}</option>`;
const res2 = await axios.get(`http://hmajax.itheima.net/api/city?pname=${province.value}`);
console.log(res2.data);
city.innerHTML = `<option value="${res2.data.list[10]}">${res2.data.list[10]}</option>`;
const res3 = await axios.get(`http://hmajax.itheima.net/api/area?pname=${province.value}&cname=${city.value}`);
console.log(res3.data);
area.innerHTML = res3.data.list.map((item) => `<option value="${item}">${item}</option>`).join('');
}
getData();
</script>
</body>
</html>try catch 捕获同步流程的错误
try 和 catch 的作用:语句标记要尝试的语句块,并指定一个出现异常时抛出的响应
jstry { // 要执行的代码 } catch (error) { // error 接收的是,错误消息 // try 里代码,如果有错误,直接进入这里执行 }try 里有报错的代码,会立刻跳转到 catch 中
尝试把代码中 url 地址写错,运行观察 try catch 的捕获错误信息能力
示例 - try catch 捕获同步流程的错误
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>35 async 函数和 await 错误捕获 获取地方列表</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body class="p-3">
<div class="container">
<h2>35 async 函数和 await 错误捕获 获取地方列表</h2>
<form class="form-group row my-5 " id="editForm">
<div class="mb-3 col">
<label for="province" class="form-label">省份</label>
<select class="form-select" id="province" name="province">
<option value="北京" selected>北京</option>
</select>
</div>
<div class="mb-3 col">
<label for="city" class="form-label">城市</label>
<select class="form-select" id="city" name="city">
<option value="北京市" selected>北京市</option>
</select>
</div>
<div class="mb-3 col">
<label for="area" class="form-label">地区</label>
<select class="form-select" id="area" name="area">
<option value="东城区" selected>东城区</option>
</select>
</div>
</form>
</div>
<!-- <script src="./index.js"></script> -->
<script src="./js/axios.min.js"></script>
<script>
/**
* 目标:async 和 await 错误捕获
*/
const province = document.querySelector('#province');
const city = document.querySelector('#city');
const area = document.querySelector('#area');
async function getData() {
// 1. try 包裹可能产生错误的代码
try {
const res1 = await axios.get('http://hmajax.itheima.net/api/province');
console.log(res1.data);
province.innerHTML = `<option value="${res1.data.list[10]}">${res1.data.list[10]}</option>`;
const res2 = await axios.get(`http://hmajax.itheima.net/api/city?pname=${province.value}`);
console.log(res2.data);
city.innerHTML = `<option value="${res2.data.list[10]}">${res2.data.list[10]}</option>`;
// const res3 = await axios.get(`http://hmajax.itheima.net/api/area?pname=${province.value}&cname=${city.value}`);
const res3 = await axios.get(`http://hmajax.itheima.net/api/area1000?pname=${province.value}&cname=${city.value}`);
console.log(res3.data);
area.innerHTML = res3.data.list.map((item) => `<option value="${item}">${item}</option>`).join('');
} catch (error) {
// 2. 接着调用 catch 块,接收错误信息
// 如果 try 里某行代码报错后,try 中剩余的代码不会执行了
console.dir(error);
}
}
getData();
</script>
</body>
</html>小结
await 的作用是什么?
- 替代
then方法来提取 Promise 对象成功状态的结果
- 替代
try和catch有什么作用?- 捕获同步流程的代码报错信息
事件循环
- 事件循环(EventLoop):掌握后知道 JS 是如何安排和运行代码的
- 作用:事件循环负责执行代码,收集和处理事件以及执行队列中的子任务
- 原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型
- 概念:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环

事件循环 - 练习
- 请根据掌握的事件循环的模型概念,分析代码执行过程
事件循环 - 练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>36 事件循环练习</title>
</head>
<body>
<script>
console.log(1); // 进入调用栈,执行同步代码,然后出栈,打印 1
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(2);
}, 0);
function myFn() {
console.log(3);
}
function ajaxFn() {
const xhr = new XMLHttpRequest(); // ajax 异步请求,将回调函数放入任务队列
xhr.open('GET', 'http://hmajax.itheima.net/api/province');
xhr.addEventListener('loadend', () => {
console.log(4);
});
xhr.send();
}
for (let i = 0; i < 1; i++) { // for 循环,执行同步代码,然后出栈,打印 5
console.log(5);
}
ajaxFn(); // ajaxFn 函数,执行同步代码,然后出栈,打印 4
document.addEventListener('click', () => { // 事件监听,用户点击后输出 6
console.log(6);
});
myFn(); // myFn 函数,执行同步代码,然后出栈,打印 3
// 调用栈:
// 1. console.log(1);
// 2. for (let i = 0; i < 1; i++) { console.log(5); }
// 3. myFn(); function myFn() { console.log(3); }
// 任务队列:
// 1. setTimeout(() => { console.log(2); }, 0);
// 宿主环境(浏览器):
// 1. xhr.addEventListener('loadend', () => { console.log(4); });
// 2. document.addEventListener('click', () => { console.log(6); });
// 打印结果:
// 1 5 3 2 4(用户点击后)6
</script>
</body>
</html>
宏任务与微任务
ES6 之后引入了 Promise 对象,让 JS 引擎也可以发起异步任务
异步任务划分为了
- 宏任务:由浏览器环境执行的异步代码
- 微任务:由 JS 引擎环境执行的异步代码
宏任务和微任务具体划分:

事件循环模型
js/** * 目标:阅读并回答打印的执行顺序 */ 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);
注意:宏任务每次在执行同步代码时,产生微任务队列,清空微任务队列任务后,微任务队列空间释放!
下一次宏任务执行时,遇到微任务代码,才会再次申请微任务队列空间放入回调函数消息排队
总结:一个宏任务包含微任务队列,他们之间是包含关系,不是并列关系
事件循环 - 经典面试题
- 需求:请切换到对应配套代码,查看具体代码,并回答打印顺序(锻炼事件循环概念的理解,阅读代码执行顺序)
事件循环 - 经典面试题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>37 事件循环面试题</title>
</head>
<body>
<script>
console.log(1); // 进入调用栈,执行同步代码
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(2);
const p = new Promise((resolve) => resolve(3)); // Promise 构造函数,执行同步代码
p.then((result) => console.log(result)); // Promise.then 回调函数,执行异步代码
}, 0);
const p = new Promise((resolve) => { // Promise 构造函数,执行同步代码
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(4);
}, 0);
resolve(5); // Promise.resolve 执行同步代码
});
p.then((result) => console.log(result)); // Promise.then 回调函数,执行异步代码
const p2 = new Promise((resolve) => resolve(6)); // Promise 构造函数,执行同步代码
p2.then((result) => console.log(result)); // Promise.then 回调函数,执行异步代码
console.log(7); // 进入调用栈,执行同步代码
// 调用栈:
// 1. console.log(1);
// 2. console.log(7);
// 宿主环境(浏览器):
// 1. setTimeout(() => { console.log(2); const p = new Promise((resolve) => resolve(3)); p.then((result) => console.log(result)); }, 0);
// 2. setTimeout(() => { console.log(4); }, 0); }
// 任务队列 - 微任务:
// 1. const p = new Promise((resolve) => { resolve(5); }).then((result) => console.log(result));
// 2. const p2 = new Promise((resolve) => resolve(6)).then((result) => console.log(result));
// 任务队列 - 宏任务:
// 1. console.log(2); const p = new Promise((resolve) => resolve(3)); p.then((result) => console.log(result));
// 2. console.log(4);
// 打印结果:
// 1 7 5 6 2 3 4
</script>
</body>
</html>
小结
什么是事件循环?
- 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里回调函数执行机制
为什么有事件循环?
- JavaScript 是单线程的,为了不阻塞 JS 引擎,设计执行代码的模型
JavaScript 内代码如何执行?
- 执行同步代码,遇到异步代码交给宿主浏览器环境执行
- 异步有了结果后,把回调函数放入任务队列排队
- 当调用栈空闲后,反复调用任务队列里的回调函数
什么是宏任务?
- 浏览器执行的异步代码
- 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX 请求完成事件,用户交互事件等
什么是微任务?
- JS 引擎执行的异步代码
- 例如:
Promise 对象.then()的回调
JavaScript 内代码如何执行?
- 执行第一个 script 脚本事件宏任务,里面同步代码
- 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列
- 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从 1 再来

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

语法:
jsconst p = Promise.all([Promise 对象,Promise 对象,...]) p.then(result => { // result 结果:[Promise 对象成功结果,Promise 对象成功结果,...] }).catch(error => { // 第一个失败的 Promise 对象,抛出的异常对象 })
案例 - 同时显示多地天气
案例 - 同时显示多地天气
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>38 同时显示多地天气 - Promise.all</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body class="p-3">
<div class="container">
<h2>38 同时显示多地天气 - Promise.all</h2>
<ul class="list-group my-5">
<li class="list-group-item">...</li>
</ul>
</div>
<script src="./js/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' } })
const getWeather = (cityCode) => {
return axios.get(`http://hmajax.itheima.net/api/weather?city=${cityCode}`);
};
const cityCodes = ['110100', '310100', '440100', '440300'];
const cityPromises = cityCodes.map((cityCode) => getWeather(cityCode));
// 2. 合并多个 Promise 对象,得到一个新的 Promise 对象
const allPromise = Promise.all(cityPromises);
// 3. 处理请求结果
allPromise
.then((res) => {
console.log(res);
document.querySelector('.list-group').innerHTML = res
.map((item) => {
return `<li class="list-group-item">【${item.data.data.area}】天气:${item.data.data.weather}</li>`;
})
.join('');
})
.catch((err) => {
console.dir(err);
});
</script>
</body>
</html>案例 - 商品分类导航
目标:把所有商品分类 "同时" 渲染到页面上
- 获取所有一级分类数据
- 遍历 id,创建获取二级分类请求
- 合并所有二级分类 Promise 对象
- 等待同时成功后,渲染页面
案例 - 商品分类导航
<!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>39 商品分类导航</title>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<!-- 大容器 -->
<div class="container">
<div class="sub-list">
<div class="item">
<h3>分类名字</h3>
<ul>
<li>
<a href="#"><img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png"
referrerpolicy="no-referrer" />
<p>巧克力</p>
</a>
</li>
</ul>
</div>
</div>
</div>
<script src="./js/axios.min.js"></script>
<script src="./js/index.js"></script>
</body>
</html>/**
* 目标:把所有商品分类“同时”渲染到页面上
* 1. 获取所有一级分类数据
* 2. 遍历 id,创建获取二级分类请求
* 3. 合并所有二级分类 Promise 对象
* 4. 等待同时成功后,渲染页面
*/
// 1. 获取所有一级分类数据
// api: http://hmajax.itheima.net/api/category/top - GET
axios.get('http://hmajax.itheima.net/api/category/top').then((res) => {
console.log(res);
// 2. 遍历 id,创建获取二级分类请求
// api: http://hmajax.itheima.net/api/category/sub/:id - GET
const subCategoryPromises = res.data.data.map((item) => {
return axios.get(`http://hmajax.itheima.net/api/category/sub?id=${item.id}`);
});
console.log(subCategoryPromises); // [Promise, Promise, Promise, Promise]
// 3. 合并所有二级分类 Promise 对象
Promise.all(subCategoryPromises).then((res) => {
console.log(res); // res: [Array, Array, Array, Array]
// 4. 等待同时成功后,渲染页面
document.querySelector('.sub-list').innerHTML = res
.map((item) => {
const category = item.data.data;
return `
<div class="item">
<h3>${category.name}</h3>
<ul>
${category.children
.map((item) => {
return `<li><a href="#"><img src="${item.picture}" referrerpolicy="no-referrer" /><p>${item.name}</p></a></li>`;
})
.join('')}
</ul>
</div>
`;
})
.join('');
});
});小结
Promise.all 什么时候使用?
- 合并多个 Promise 对象并等待所有同时成功的结果,如果有一个报错就会最终为失败状态,当需要同时渲染多个接口数据同时到网页上时使用
今日重点
掌握 async 和 await 的使用
async函数是用来定义一个返回 Promise 对象的函数。在函数体内部,可以使用await来暂停函数的执行,等待 Promise 对象的解决。jsasync function exampleAsyncFunction() { console.log('Start'); // 使用 await 暂停函数执行,等待 Promise 对象解决 const result = await new Promise((resolve) => { setTimeout(() => { resolve('Async operation completed'); }, 1000); }); console.log(result); console.log('End'); } exampleAsyncFunction();await关键字只能在async函数内部使用。它用于等待一个 Promise 对象的解决。当遇到await时,async函数会暂停执行,直到 Promise 对象解决。在上述例子中,await会等待setTimeout的延时操作完成。- 需要注意的是,使用
await时需要确保其所在的函数是async函数,否则会导致语法错误。
jsasync function exampleAsyncFunction() { console.log('Start'); const result = await someAsyncOperation(); // 必须在 async 函数内使用 await console.log(result); console.log('End'); }- 需要注意的是,使用
使用
async和await可以更清晰地处理 Promise 的链式调用,避免了回调地狱(Callback Hell)。jsasync function fetchData() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); } catch (error) { console.error('Error fetching data:', error); } } fetchData(); // fetchData 函数使用 await 依次等待 fetch 和 response.json() 的结果,使得异步操作看起来像同步代码一样,更易读
理解 EventLoop 和宏任务微任务执行顺序
事件循环(Event Loop)是一个用于处理异步操作的机制。
在事件循环中,任务被分为两类:宏任务(Macro Task)和微任务(Micro Task)。它们分别被放入不同的执行队列,并按照一定的规则执行。
Event Loop 执行过程
- 执行同步任务(Synchronous Tasks): 从上到下依次执行脚本中的同步代码。
- 执行微任务(Micro Tasks): 当执行栈空闲时,依次执行微任务队列中的所有任务。
- 执行宏任务(Macro Tasks): 清空微任务队列后,从宏任务队列中取出一个任务执行。
了解 Promise.all 的作用和使用场景
Promise.all是一个用于处理多个 Promise 并行执行的工具方法。它接收一个包含多个 Promise 的可迭代对象(通常是数组),并返回一个新的 Promise。这个新 Promise 会在所有输入的 Promise 都成功解决(resolved)时被解决,或者在任意一个输入的 Promise 被拒绝(rejected)时被拒绝。
Promise.all返回的 Promise 的解决值是一个包含所有输入 Promise 解决值的数组。使用场景:
- 并行执行多个异步任务: 当有多个异步任务可以并行执行,并且你希望等待所有任务完成后再执行下一步操作时,
Promise.all是一个很有用的工具。 - 等待多个接口请求完成: 在前端开发中,有时需要从多个接口获取数据,然后进行下一步操作,使用
Promise.all可以在所有数据都准备好时触发后续逻辑。
- 并行执行多个异步任务: 当有多个异步任务可以并行执行,并且你希望等待所有任务完成后再执行下一步操作时,
完成案例 - 学习反馈
html<!DOCTYPE html> <html lang="zh-CN"> <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"> <!-- 初始化样式 --> <link rel="stylesheet" href="./css/reset.min.css"> <!-- 引入 bootstrap.css --> <link href="./css/bootstrap.min.css" rel="stylesheet"> <!-- 核心样式 --> <link rel="stylesheet" href="./css/index-min.css"> <title>40 学习反馈</title> </head> <body> <div class="container"> <div class="toast position-fixed top-0 start-50 translate-middle-x" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="2000"> <div class="toast-body"> 操作成功 </div> </div> <h4 class="stu-title">学习反馈</h4> <img class="bg" src="./img/head.webp" alt=""> <div class="item-wrap"> <div class="hot-area"> <span class="hot">热门校区</span> <ul class="nav"> <li><a target="_blank" href="http://bjcp.itheima.com/">北京</a> </li> <li><a target="_blank" href="http://sh.itheima.com/">上海</a> </li> <li><a target="_blank" href="http://gz.itheima.com/">广州</a> </li> <li><a target="_blank" href="http://sz.itheima.com/">深圳</a> </li> </ul> </div> <form class="info-form"> <div class="area-box"> <span class="title">地区选择</span> <select name="province" class="province"> <option value="">省份</option> </select> <select name="city" class="city"> <option value="">城市</option> </select> <select name="area" class="area"> <option value="">地区</option> </select> </div> <div class="area-box"> <span class="title">您的称呼</span> <input type="text" name="nickname" class="nickname" value="播仔"> </div> <div class="area-box"> <span class="title">宝贵建议</span> <textarea type="text" name="feedback" class="feedback" placeholder="您对AJAX阶段课程宝贵的建议"></textarea> </div> <div class="area-box"> <button type="button" class="btn btn-secondary submit"> 确定提交 </button> </div> </form> </div> </div> <script src="./js/axios.min.js"></script> <script src="./js/form-serialize.js"></script> <script src="./js/bootstrap.min.js"></script> <!-- 核心代码 --> <script src="./js/render.js"></script> <script src="./js/submit.js"></script> </body> </html>js/** * 目标 1:完成省市区下拉列表切换 * 1.1 设置省份下拉菜单数据 * 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单 * 1.3 切换城市,设置地区下拉菜单数据 */ // 1 设置省份下拉菜单数据 const provinceElement = document.querySelector('.province'); const cityElement = document.querySelector('.city'); const aeraElement = document.querySelector('.area'); axios .get('http://hmajax.itheima.net/api/province') .then((res) => { console.log(res); provinceElement.innerHTML = `<option value="">请选择省份</option>${res.data.list .map((item) => `<option value="${item}">${item}</option>`) .join('')}`; }) .catch((error) => { console.dir(error); }); // 2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单 provinceElement.addEventListener('change', async function () { const province = this.value; try { const res = await axios.get(`http://hmajax.itheima.net/api/city?pname=${province}`); console.log(res); cityElement.innerHTML = `<option value="">请选择城市</option>${res.data.list .map((item) => `<option value="${item}">${item}</option>`) .join('')}`; aeraElement.innerHTML = '<option value="">请选择区县</option>'; } catch (error) { console.dir(error); } }); // 3 切换城市,设置地区下拉菜单数据 cityElement.addEventListener('change', async function () { const city = this.value; try { const res = await axios.get(`http://hmajax.itheima.net/api/area?pname=${provinceElement.value}&cname=${city}`); console.log(res); aeraElement.innerHTML = `<option value="">请选择区县</option>${res.data.list .map((item) => `<option value="${item}">${item}</option>`) .join('')}`; } catch (error) { console.dir(error); } });js/** * 目标 2:收集数据提交保存 * 2.1 监听提交的点击事件 * 2.2 依靠插件收集表单数据 * 2.3 基于 axios 提交保存,显示结果 */ // 1 监听提交的点击事件 const submitBtn = document.querySelector('.submit'); submitBtn.addEventListener('click', async () => { // 2 依靠插件收集表单数据 const form = document.querySelector('.info-form'); const formData = serialize(form, { hash: true, empty: true }); const toastDom = document.querySelector('.toast'); const toast = new bootstrap.Toast(toastDom); // toastDom.querySelector('.toast-body').style.backgroundColor = 'var(--bs-primary)'; toastDom.querySelector('.toast-body').classList.add('p-3', 'rounded-3'); // 3 基于 axios 提交保存,显示结果 try { const res = await axios.post('http://hmajax.itheima.net/api/feedback', formData); console.log(res); // HTTP 状态码:200 if (res.status === 200) { // alert(res.data.message); toastDom.querySelector('.toast-body').textContent = res.data.message; toastDom.querySelector('.toast-body').style.backgroundColor = 'var(--bs-success)'; toast.show(); } } catch (err) { console.dir(err); toastDom.querySelector('.toast-body').textContent = err.response.data.message; toastDom.querySelector('.toast-body').style.backgroundColor = 'var(--bs-danger)'; toast.show(); } });
今日作业
客观题
在线答题:Day04_AJAX 进阶
以下哪个代码能正确运行?
jsconst p1 = new Promise((resolve, reject) => { resolve(1); }); const res1 = await p1;jsconst p2 = new Promise((resolve, reject) => { reject(2); }); const res2 = await p2;jsconst p3 = new Promise((resolve, reject) => { resolve(3) }) async const myFn = () => { const res3 = await p3 } myFn()jsconst p4 = new Promise((resolve, reject) => { resolve(4); }); const myFn4 = async () => { const res4 = await p4; }; myFn4();答案
- 答案是 B。在 JavaScript 中,
await关键字只能在async函数内部使用。选项 B 中的代码使用了正确的语法结构,即在async函数内使用await。 - 选项 A 没有将
await放在async函数内部。 - 选项 C 中的
async const myFn = () => {…}缺少括号。 - 选项 D 中的
const p4 = new Promise(…}缺少括号,且async const myFn4 = () => {…}缺少括号。
- 答案是 B。在 JavaScript 中,
以下代码输出结果是?
jsconst fn = async () => { const res = await 10; console.log(res); }; fn();- A. 报错
- B.
10 - C.
await后面不是 Promise, 所以执行失败 - D.
undefined
答案
- 答案是 B. 10。
- 在这个代码片段中,
await后面紧跟的是一个非 Promise 对象(数字 10),但是在async函数内部,非 Promise 对象会被自动包装成一个resolved的 Promise 对象。因此,整个async函数会正常执行,并输出结果。 - 所以,输出结果是 10。
以下代码输出结果是?
jslet p = new Promise((resolve, reject) => { resolve(1000); }); let p2 = new Promise((resolve, reject) => { reject(1000); }); const theFn = async () => { const res = await p; console.log(res); const res2 = await p2; console.log(res2); }; theFn();- A. 1000, 1000
- B. 报错,一个都不打印
- C. 不报错,但也不打印
- D. 1000, 报错
答案
- 答案是 D. 1000, 报错
- 在这个代码片段中,第一个
await p会正常执行并输出 1000,但第二个await p2由于p2是一个rejected的 Promise,会导致 Promise 的状态变为rejected,触发await后面的代码块的异常处理。 - 因此,输出结果是 1000,然后会抛出一个异常,但由于
theFn函数是一个异步函数,所以不会影响整个程序的执行。
以下代码能正确捕获到异常的是?
jslet p = new Promise((resolve, reject) => { reject(new Error('请检查')); }); // A: // async const fnA = () => { // try { // const res = await p // } catch (err) { // console.error(err) // } // } // B: // const fnB = async () => { // try { // } catch (err) { // const res = await p; // console.error(err); // } // }; // C: // const fnC = async () => { // try { // const res = await p; // } catch { // console.error(err); // } // }; // D: // const fnD = async () => { // try { // const res = await p; // } catch (err) { // console.error(err); // } // };答案
- 答案是 D.在这个代码片段中,
await p处会触发reject状态,进入到catch块中,正确捕获到异常并输出错误信息。 - 选项 A 中的
async const是不正确的语法。 - 选项 B 中的
try块为空,没有实际的异步操作。 - 选项 C 中的
catch块没有声明err参数。
- 答案是 D.在这个代码片段中,
以下代码都有哪些任务?
jsconsole.log(1); setTimeout(() => { console.log(2); }, 0); let p = new Promise((resolve, reject) => { resolve(3); }); p.then((res) => { console.log(res); }); console.log(4);- A. 只有同步任务
- B. 只有异步任务
- C. 同步任务,微任务,宏任务
- D. 只有宏任务和微任务
答案
答案是 C. 同步任务、微任务(Promise 的回调函数)、宏任务(setTimeout 回调函数)。
console.log(1);是一个同步任务,会在主线程上立即执行。setTimeout(() => { console.log(2); }, 0);是一个宏任务,会被放入任务队列中,等待主线程任务执行完毕后执行。let p = new Promise((resolve, reject) => { resolve(3); });是一个同步任务,Promise 的执行是同步的,但它的then方法中的回调是微任务,会在当前任务执行完成后立即执行。console.log(4);是一个同步任务,会在主线程上立即执行。
所以,该代码包含同步任务、微任务(Promise 的回调函数)、宏任务(setTimeout 回调函数)。
以下代码打印结果是?
js// 读题回答打印顺序 console.log(1); myFn(); setTimeout(() => { theFn(); }, 0); new Promise((resolve, reject) => { resolve(2); }).then((res) => { console.log(res); }); function myFn() { console.log(3); } const theFn = () => { console.log(4); };- A. 1, 2, 4, 3
- B. 1, 3, 4, 2
- C. 1, 2, 3, 4
- D. 1, 3, 2, 4
答案
答案是 B. 1, 3, 4, 2。
console.log(1);是一个同步任务,会在主线程上立即执行。myFn();是一个同步任务,会在主线程上立即执行,打印 3。setTimeout(() => { theFn(); }, 0);是一个宏任务,会被放入任务队列中,等待主线程任务执行完毕后执行。new Promise((resolve, reject) => { resolve(2); }).then((res) => { console.log(res); });是一个同步任务,Promise 的执行是同步的,但它的then方法中的回调是微任务,会在当前任务执行完成后立即执行,打印 2。theFn是一个函数声明,会被提升到作用域顶部,所以在setTimeout中能够访问到,theFn();执行时打印 4。
因此,输出结果是:1, 3, 4, 2。
以下代码打印结果是?
js// 读题回答打印顺序 async function async1() { console.log(1); await async2(); console.log(2); } async function async2() { return new Promise((resolve, reject) => { reject(new Error('')); }); } console.log(3); setTimeout(function () { console.log(4); }, 0); async1(); new Promise(function (resolve) { console.log(5); resolve(); }).then(function () { console.log(6); }); console.log(7);- A. 3, 1, 7, 5, 6, 4
- B. 3, 1, 5, 7, 6, 4
- C. 3, 5, 1, 7, 6, 4
- D. 3, 6, 1, 7, 5, 4
答案
答案是 A. 3, 1, 7, 5, 6, 4。
console.log(3);是一个同步任务,会在主线程上立即执行。setTimeout(function () { console.log(4); }, 0);是一个宏任务,会被放入任务队列中,等待主线程任务执行完毕后执行。async1();被调用,async1是一个异步函数,其中的console.log(1);是同步执行的,然后遇到await async2();,async2返回的 Promise 是 rejected 状态,触发async1中的异常,但由于异步函数的特性,异常会被 Promise 包装,不会中断主线程,所以会继续执行console.log(2);。new Promise(function (resolve) { console.log(5); resolve(); }).then(function () { console.log(6); });是一个同步任务,Promise 的执行是同步的,但它的then方法中的回调是微任务,会在当前任务执行完成后立即执行,打印 5 和 6。- 最后执行
setTimeout中的回调,打印 4。 console.log(7);是一个同步任务,会在主线程上立即执行。
所以,输出结果是:3, 1, 7, 5, 6, 4。
以下代码打印结果是?
js// 读题回答打印顺序 setTimeout(() => { console.log(1); new Promise((resolve, reject) => { resolve(2); }).then((res) => { console.log(res); setTimeout(() => { console.log(3); }, 1000); }); }, 0); console.log(4); setTimeout(() => { console.log(5); }, 5000); console.log(6);- A. 4, 1, 6, 2, 3, 5
- B. 4, 6, 1, 2, 5, 3
- C. 4, 6, 1, 2, 3, 5
- D. 4, 6, 2, 1, 3, 5
答案
答案是 C. 4, 6, 1, 2, 3, 5。
- 首先执行第一个
setTimeout,打印 1。然后执行内部的 Promise,由于是 resolved 状态,执行其then回调,打印 2,并设置另一个setTimeout,打印 3。 - 继续执行主线程,打印 4。
- 执行第二个
setTimeout,由于设置了延时为 5000 毫秒,所以会在 5000 毫秒后执行,打印 5。 - 最后打印 6。
- 首先执行第一个
所以,输出结果是:4, 6, 1, 2, 3, 5。
以下代码打印结果是?
js// 读题回答打印顺序 new Promise((resolve, reject) => { console.log(1); new Promise((resolve, reject) => { console.log(2); setTimeout(() => { console.log(3); }, 0); console.log(4); }); console.log(5); }); setTimeout(() => { console.log(6); }, 1000); console.log(7);- A. 1, 2, 4, 5, 3, 7, 6
- B. 1, 3, 4, 5, 7, 2, 6
- C. 1, 2, 5, 4, 7, 3, 6
- D. 1, 2, 4, 5, 7, 3, 6
答案
答案是 C. 1, 2, 5, 4, 7, 3, 6。
- 首先执行外部的 Promise,打印 1。然后执行内部的 Promise,打印 2。
- 在内部的 Promise 中,遇到
setTimeout,将其放入宏任务队列中,接着打印 4。 - 继续执行外部的 Promise,打印 5。
- 主线程任务执行完成后,开始执行宏任务队列中的
setTimeout,打印 3。 - 执行最后的
setTimeout,由于设置了延时为 1000 毫秒,所以会在 1000 毫秒后执行,打印 6。 - 最后打印 7。
所以,输出结果是:1, 2, 5, 4, 7, 3, 6。
以下代码打印结果是?
jsconsole.log(1); setTimeout(() => { console.log(2); }, 0); console.log(3); console.log(4); setTimeout(() => { console.log(5); }, 0); console.log(6);- A. 1, 3, 6, 4, 2, 5
- B. 1, 3, 4, 6, 2, 5
- C. 1, 4, 3, 6, 2, 5
- D. 1, 3, 4, 2, 6, 5
答案
答案是 D. 1, 3, 4, 2, 6, 5。
- 执行第一个
console.log(1);,打印 1。 - 执行第一个
setTimeout,将其放入宏任务队列中,继续执行同步任务,打印 3。 - 打印 4。
- 主线程同步任务执行完成后,开始执行宏任务队列中的
setTimeout,打印 2。 - 继续执行同步任务,打印 6。
- 执行第二个
setTimeout,将其放入宏任务队列中,但由于前面的任务已经执行完成,所以可以立即执行,打印 5。
- 执行第一个
所以,输出结果是:1, 3, 4, 2, 6, 5。
主观题
作业 1 - 事件循环面试题
<!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>事件循环经典 经典面试题 1</title>
</head>
<body>
<script>
console.log(1) // 进入调用栈,执行同步代码
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(2)
}, 0)
console.log(3) // 进入调用栈,执行同步代码
</script>
<script>
console.log(4) // 进入调用栈,执行同步代码
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(5)
}, 0)
console.log(6) // 进入调用栈,执行同步代码
// 调用栈:
// 1. console.log(1);
// 2. console.log(3);
// 3. console.log(4);
// 4. console.log(6);
// 宿主环境(浏览器)
// 1. setTimeout(() => { console.log(2); }, 0);
// 2. setTimeout(() => { console.log(5); }, 0);
// 任务队列 - 宏任务:
// 1. console.log(2);
// 2. console.log(5);
// 打印结果:
// 1 3 4 6 2 5
</script>
</body>
</html><!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>事件循环经典 经典面试题 2</title>
</head>
<body>
<!-- 今日头条 -->
<!-- 提示:await 右结合,右边代码执行后原地等待(await 取代 then 函数,相当于微任务) -->
<script>
console.log(1) // 进入调用栈,执行同步代码
async function fnOne() {
console.log(2)
await fnTwo() // 右结合先执行右侧的代码,然后等待
console.log(3)
}
async function fnTwo() {
console.log(4)
}
fnOne()
setTimeout(() => { // 宿主环境(浏览器),2s 后将回调函数放入任务队列
console.log(5)
}, 2000)
let p = new Promise((resolve, reject) => { // new Promise() 里的函数体会马上执行所有代码
console.log(6)
resolve()
console.log(7)
})
setTimeout(() => { // 宿主环境(浏览器),0s 后将回调函数放入任务队列
console.log(8)
}, 0)
p.then(() => { // Promise.then 回调函数,执行异步代码
console.log(9)
})
console.log(10) // 进入调用栈,执行同步代码
// 调用栈
// 1. console.log(1)
// 2. fnOne() { console.log(2) }
// 3. fnTwo() { console.log(4) }
// 4. let p = new Promise((resolve, reject) => { console.log(6); resolve(); console.log(7); }
// 5. console.log(10)
// 任务队列 - 宏任务:
// 1. setTimeout(() => { console.log(5) }, 2000)
// 2. setTimeout(() => { console.log(8) }, 0)
// 任务队列 - 微任务:
// 1. console.log(3)
// 2. p.then(() => { console.log(9) })
// 打印结果:
// 1 2 4 6 7 10 3 9 8 5
</script>
</body>
</html><!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>事件循环经典 经典面试题 3</title>
</head>
<body style="width: 100%; height: 100vh;">
<!-- 需要给 body 设置宽高,如果没有设置,则无法点击,绑定的点击事件会无效 -->
<script>
// Twitter 面试题 - 20% 答对
document.body.addEventListener('click', (e) => {
let p = new Promise(resolve => resolve(1))
p.then(result => console.log(result)) // 微任务:Promise 的回调函数(即 then 方法中的函数)是异步代码
console.log(2) // 同步代码
})
document.body.addEventListener('click', () => {
let p = new Promise(resolve => resolve(3))
p.then(result => console.log(result))
console.log(4)
})
// 调用栈:
// 1. console.log(2);
// 微任务:
// 1. p.then(result => console.log(result))
// 打印结果:
// (用户点击后) 2 1 4 3
</script>
</body>
</html>作业 2 - 评论列表
目标:完成如下评论列表效果
要求:
- 默认上来展示所有评论列表数据(注意不区分用户了)感受下大家数据互相影响,也可以看到别人评论
- 新增评论功能
- 删除评论功能
- 分页切换评论列表数据功能
- 删除最后一条评论,列表要自动回到上一页
接口文档:https://apifox.com/apidoc/shared-1b0dd84f-faa8-435d-b355-5a8a329e34a8/api-82668108
配套资料:配套标签和样式在文件夹内
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>评论列表</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
<style>
.badge {
float: right;
margin-right: 5px;
}
.my-page {
margin: 0 5px;
}
.all-page-content {
margin-left: 5px;
}
</style>
</head>
<body style="padding: 15px;">
<!-- 评论面板 -->
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">发表评论</h3>
</div>
<form class="panel-body cmt-form">
<div>评论人:</div>
<input type="text" class="form-control" name="username" autocomplete="off" />
<div>评论内容:</div>
<textarea class="form-control" name="content"></textarea>
<button type="submit" class="btn btn-primary submit">发表评论</button>
</form>
</div>
<!-- 评论列表 -->
<ul class="list-group">
<!-- <li class="list-group-item">
<span>评论内容</span>
<span class="badge del" style="cursor:pointer; background-color: lightgray;">删除</span>
<span class="badge" style="background-color: #F0AD4E;">评论时间:xxx</span>
<span class="badge" style="background-color: #5BC0DE;">评论人:xxx</span>
</li> -->
</ul>
<!-- 分页器 -->
<nav>
<ul class="pagination">
<li>
<button class="last">
<span>«</span>
</button>
</li>
<li class="my-page">
<span class="page-show"></span>
</li>
<li>
<button class="next">
<span>»</span>
</button>
</li>
<li class="all-page-content">
<span>共计:<span class="all-page"></span>页</span>
</li>
</ul>
</nav>
<script src="./js/axios.min.js"></script>
<script src="./js/form-serialize.js"></script>
<script src="./js/getComment.js"></script>
<script src="./js/addComment.js"></script>
<script src="./js/delComment.js"></script>
</body>
</html>// 1 默认上来展示所有评论列表数据(注意不区分用户了)感受下大家数据互相影响,也可以看到别人评论
// api: https://hmajax.itheima.net/api/cmtlist?page=${nowPage} GET
let nowPage = 1;
let allPage;
const listGroup = document.querySelector('.list-group');
function fetchCommentList() {
axios.get(`https://hmajax.itheima.net/api/cmtlist?page=${nowPage}`).then((res) => {
console.log(res);
// 总页码数
allPage = res.data.allPage;
document.querySelector('.all-page').innerText = allPage;
// 清空列表
listGroup.innerHTML = '';
// 渲染列表
listGroup.innerHTML = res.data.data
.map((item) => {
return `<li class="list-group-item">
<span>${item.content}</span>
<span class="badge del" style="cursor:pointer; background-color: lightgray;" data-id=${item.id}>删除</span>
<span class="badge" style="background-color: #F0AD4E;">评论时间:${item.time}</span>
<span class="badge" style="background-color: #5BC0DE;">评论人:${item.username}</span>
</li>`;
})
.join('');
// 设置页码
document.querySelector('.page-show').innerHTML = nowPage;
});
}
// 页面加载时默认拉取第一页的评论数据
fetchCommentList();
// 绑定 上一页/下一页按钮的点击事件
const lastElement = document.querySelector('.last');
const nextElement = document.querySelector('.next');
// 分页切换评论列表数据功能
if (lastElement && nextElement) {
lastElement.addEventListener('click', () => {
nowPage > 1 ? nowPage-- && fetchCommentList() : console.log('已经是第一页了');
});
nextElement.addEventListener('click', () => {
nowPage < allPage ? nowPage++ && fetchCommentList() : console.log('已经是最后一页了');
});
}// 新增评论功能
// api: https://hmajax.itheima.net/api/addcmt POST
// data:username content
const submitElement = document.querySelector('.submit');
submitElement.addEventListener('click', async (e) => {
e.preventDefault(); // 阻止默认行为
// 获取表单数据
const form = document.querySelector('.cmt-form');
const formData = serialize(form, { hash: true, empty: true });
try {
const response = await axios.post('https://hmajax.itheima.net/api/addcmt', formData);
console.log(response);
// 重新拉取评论列表数据
nowPage = 1;
fetchCommentList();
// 表单复位
form.reset();
} catch (error) {
console.error(error);
}
});// 删除评论功能
// 删除最后一条评论,列表要自动回到上一页
// api: https://hmajax.itheima.net/api/delcmt?id=${id} GET
listGroup.addEventListener('click', async (e) => {
console.log(e.target);
if (e.target.classList.contains('del')) {
try {
const response = await axios.get(`https://hmajax.itheima.net/api/delcmt?id=${e.target.getAttribute('data-id')}`);
console.log(response);
allPage = response.data.allPage;
nowPage = nowPage > allPage ? allPage : nowPage;
fetchCommentList();
} catch (error) {
console.dir(error);
}
}
});