目录
1.同步代码和异步代码
2.回调函数地狱和 Promise 链式调用
2.1 回调函数地狱
2.2 Promise - 链式调用
2.3 Promise 链式应用
3.async 和 await 使用
3.1 async函数和await
3.2 async函数和await_捕获错误
4.事件循环-EventLoop
4.1 事件循环
4.2 宏任务与微任务- 执行顺序
4.3 总结
5.Promise.all 静态方法
6.案例 - 商品分类
7.案例 - 学习反馈
7.1 完成省市区切换效果
7.2 收集学习反馈数据,提交保存
1.同步代码和异步代码
小结:
2.回调函数地狱和 Promise 链式调用
2.1 回调函数地狱
案例:
需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中
2.2 Promise - 链式调用
2.3 Promise 链式应用
代码:
<script>
/**
* 目标:把回调函数嵌套代码,改成Promise链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
let pname = ''
axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => {
// 获取第一个省份
// console.log(result.data.list[0])
const province = document.querySelector('.province')
pname = result.data.list[0]
province.innerHTML = result.data.list[0]
// 返回城市的对象
return axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
}).then(result => {
// console.log(result.data.list[0])
// 获取第一个城市
const city = document.querySelector('.city')
const cname = result.data.list[0]
city.innerHTML = result.data.list[0]
// 返回地区信息
return axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
}).then(result => {
// 获取第一个地区
const area = document.querySelector('.area')
area.innerHTML = result.data.list[0]
})
</script>
小结:
3.async 和 await 使用
3.1 async函数和await
目标:掌握async和await语法,解决回调函数地狱
定义:
async
函数是 JavaScript 的一种特殊函数,它可以在函数中使用 await
关键字。当你将一个普通函数用 async
关键字修饰后,这个函数默认返回一个 Promise
对象。普通的返回值会被封装成一个 fulfilled
状态的 Promise
对象,而抛出的任何异常都会被封装成 rejected
状态的 Promise
对象。
注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
代码:
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
async function getData() {
const p = await axios({ url: 'http://hmajax.itheima.net/api/province' })
const pname = p.data.list[0]
// console.log(p.data.list[0])
const c = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
const cname = c.data.list[0]
// console.log(c.data.list[0])
const a = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
const aname = a.data.list[0]
console.log(a)
// 写入html文档
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = aname
}
getData()
</script>
3.2 async函数和await_捕获错误
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
async function getData() {
try {
const p = await axios({ url: 'http://hmajax.itheima.net/api/province' })
const pname = p.data.list[0]
// console.log(p.data.list[0])
const c = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
const cname = c.data.list[0]
// console.log(c.data.list[0])
const a = await axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })
const aname = a.data.list[0]
console.log(a)
// 写入html文档
document.querySelector('.province').innerHTML = pname
document.querySelector('.city').innerHTML = cname
document.querySelector('.area').innerHTML = aname
} catch (error) {
console.dir(error)
}
}
getData()
</script>
捕捉到异常之后,异常后面的代码将不再执行。
4.事件循环-EventLoop
4.1 事件循环
定义:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环。
原理:
-
调用栈(Call Stack):
- 调用栈是执行代码的地方。它记录了当前正在执行的函数和函数调用的历史。当一个函数被调用时,它会被压入调用栈中,执行完成后,它会被从栈中弹出。
-
任务队列(Task Queue 或 Callback Queue):
- 任务队列用于存放异步任务的回调函数。例如,当
setTimeout
函数的定时器结束时,它的回调函数会被放到任务队列中。
- 任务队列用于存放异步任务的回调函数。例如,当
-
微任务队列(Microtask Queue):
- 微任务队列存放的是微任务(例如 Promise 的回调函数)。微任务通常比宏任务(如定时器回调)优先级更高。微任务队列的任务会在事件循环的每一轮结束之前执行完毕。
4.2 宏任务与微任务- 执行顺序
- ✓ 宏任务:由浏览器环境执行的异步代码
- ✓ 微任务:由 JS 引擎环境执行的异步代码
使用图解-分析代码执行顺序
简单理解:同步就是会被立即执行的代码,而异步会被分配到相应的任务队列进行等待,
当栈处于空闲状态就会先清空微任务队列,再执行宏任务队列。
4.3 总结
- ➢ 浏览器执行的异步代码
- ➢ 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成 事件,用户交互事件等
- ➢ JS 引擎执行的异步代码
- ➢ 例如:Promise对象.then()的回调
- ➢ 执行第一个 script 脚本事件宏任务,里面同步代码
- ➢ 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列
- ➢ 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来
5.Promise.all 静态方法
<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>
6.案例 - 商品分类
商品分类
- 1. 获取所有的一级分类数据
- 2. 遍历id,创建获取二级分类请求
- 3. 合并所有二级分类Promise对象
- 4. 等待同时成功,开始渲染页面
效果图:
代码:
<body>
<!-- 大容器 -->
<div class="container">
<div class="sub-list">
<div class="item">
<h3>分类名字</h3>
<ul>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
<li>
<a href="javascript:;">
<img src="http://zhoushugang.gitee.io/erabbit-client-pc-static/uploads/img/category%20(9).png" />
<p>巧克力</p>
</a>
</li>
</ul>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把所有商品分类“同时”渲染到页面上
* 1. 获取所有一级分类数据
* 2. 遍历id,创建获取二级分类请求
* 3. 合并所有二级分类Promise对象
* 4. 等待同时成功后,渲染页面
*/
axios({
url: 'http://hmajax.itheima.net/api/category/top'
}).then(result => {
// 获取所有一级分类数据
console.log(result.data.data)
// 遍历id,创建获取二级分类请求, 这里得到的是所有二级分类的promise对象数组
const firstObj = result.data.data.map(item => {
// console.log(item.id)
return axios({
url: 'http://hmajax.itheima.net/api/category/sub',
params: { id: item.id }
})
})
// console.log(firstObj)
// 合并所有二级分类Promise对象
const secondObj = Promise.all(firstObj)
secondObj.then(result => {
console.log(result)
// 等待同时成功后,渲染页面
const thirdObj = result.map(item => {
const dataObj = item.data.data
// 这里拼接上一级分类和循环遍历一级分类下的二级分类
return `
<div class="item">
<h3>${dataObj.name}</h3>
<ul>
${dataObj.children.map(i => {
return `
<li>
<a href="javascript:;">
<img src=${i.picture} />
<p>${i.name}</p>
</a>
</li>
`
}).join('')}
</ul>
</div>
`
}).join('')
document.querySelector('.sub-list').innerHTML = thirdObj
})
})
</script>
</body>
7.案例 - 学习反馈
7.1 完成省市区切换效果
- 1. 设置省份数据到下拉菜单
- 2. 切换省份,设置城市数据到下拉菜单,并清空地区下拉菜单
- 3. 切换城市,设置地区数据到下拉菜单
页面展示:
代码:
/**
* 目标1:完成省市区下拉列表切换
* 1.1 设置省份下拉菜单数据
* 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
* 1.3 切换城市,设置地区下拉菜单数据
*/
// 1.1 设置省份下拉菜单数据
axios({
url: 'http://hmajax.itheima.net/api/province'
}).then(result => {
// result得到省份对象
console.log(result)
const pnameObj = result.data.list.map(pname => {
return `<option value="${pname}">${pname}</option>`
}).join('')
document.querySelector('.province').innerHTML = '<option value="">省份</option>' + pnameObj
})
let pname = ''
// 1.2 切换省份,设置城市下拉菜单数据,清空地区下拉菜单
document.querySelector('.province').addEventListener('change', e => {
// 获取到当前对象的value值
// console.log(e.target.value)
pname = e.target.value
axios({
url: 'http://hmajax.itheima.net/api/city',
params: { pname }
}).then(result => {
// result得到城市的对象
// console.log(result.data.list)
const cnameObj = result.data.list.map(cname => {
return ` <option value="${cname}">${cname}</option>`
}).join('')
document.querySelector('.city').innerHTML = ' <option value="">城市</option>' + cnameObj
// 清空地区下拉菜单
document.querySelector('.area').innerHTML = '<option value="">地区</option>'
})
})
// 1.3 切换城市,设置地区下拉菜单数据
document.querySelector('.city').addEventListener('change', e => {
// 获取到当前对象的value值
// console.log(e.target.value)
axios({
url: 'http://hmajax.itheima.net/api/area',
params: { pname, cname: e.target.value }
}).then(result => {
// result得到地区的对象
// console.log(result.data.list)
const anameObj = result.data.list.map(aname => {
return `<option value="${aname}">${aname}</option>`
}).join('')
document.querySelector('.area').innerHTML = '<option value="">地区</option>' + anameObj
})
})
7.2 收集学习反馈数据,提交保存
- 1. 监听提交按钮的点击事件
- 2. 依靠插件收集表单数据
- 3. 基于 axios 提交保存,显示结果
页面展示:
代码:
/**
* 目标2:收集数据提交保存
* 2.1 监听提交的点击事件
* 2.2 依靠插件收集表单数据
* 2.3 基于axios提交保存,显示结果
*/
// 2.1 监听提交的点击事件
document.querySelector('.submit').addEventListener('click', () => {
// 2.2 依靠插件收集表单数据
const form = document.querySelector('.info-form')
const obj = serialize(form, { hash: true, empty: true })
// 得到表单的提交数据,该数据为一个对象
// area: "河东区",city: "天津市"
console.log(obj)
// 2.3 基于axios提交保存,显示结果
axios({
url: 'http://hmajax.itheima.net/api/feedback',
method: 'POST',
data: obj
}).then(result => {
console.log(result)
alert(result.data.message)
}).catch(error => {
console.dir(error)
alert(error.response.data.message)
})
})