目录
[2.回调函数地狱和 Promise 链式调用](#2.回调函数地狱和 Promise 链式调用)
[2.1 回调函数地狱](#2.1 回调函数地狱)
[2.2 Promise - 链式调用](#2.2 Promise - 链式调用)
[2.3 Promise 链式应用](#2.3 Promise 链式应用)
[3.async 和 await 使用](#3.async 和 await 使用)
[3.1 async函数和await](#3.1 async函数和await)
[3.2 async函数和await_捕获错误](#3.2 async函数和await_捕获错误)
[4.1 事件循环](#4.1 事件循环)
[4.2 宏任务与微任务- 执行顺序](#4.2 宏任务与微任务- 执行顺序)
[4.3 总结](#4.3 总结)
[5.Promise.all 静态方法](#5.Promise.all 静态方法)
[6.案例 - 商品分类](#6.案例 - 商品分类)
[7.案例 - 学习反馈](#7.案例 - 学习反馈)
[7.1 完成省市区切换效果](#7.1 完成省市区切换效果)
[7.2 收集学习反馈数据,提交保存](#7.2 收集学习反馈数据,提交保存)
1.同步代码和异步代码
同步代码: 逐行 执行,需 原地等待 结果 后,才继续向下执行。
异步代码:调用后 耗时 ,不阻塞代码继续执行(不必原地等待),在 将来 完成后触发一个 回调函数
小结:
- 什么是同步代码?
➢ 逐行执行, 原地等待 结果 后,才继续向下执行 - 什么是异步代码?
➢ 调用后 耗时 ,不阻塞代码执行,将来完成后触发 回调函数 - JS 中有哪些异步代码?
➢ setTimeout / setInterval
➢ 事件
➢ AJAX - 异步代码如何接收结果?
➢ 依靠 回调函数 来接收
2.回调函数地狱和 Promise 链式调用
2.1 回调函数地狱
概念:在回调函数中 嵌套回调函数 ,一直嵌套下去就形成了回调函数地狱
缺点:可读性差,异常无法捕获(内层的错误信息,外层无法捕获),耦合性严重,牵一发动全身
案例:
需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中
2.2 Promise - 链式调用
概念:依靠 then() 方法会返回一个 新生成的 Promise 对象 特性,继续串联下一环任务,直到结束
细节:then() 回调函数中的 返回值 ,会影响新生成的 Promise 对象 最终状态和结果
好处:通过链式调用,解决回调函数嵌套问题
2.3 Promise 链式应用
目标:使用 Promise 链式调用,解决回调函数地狱问题
做法:每个 Promise 对象中管理一个异步任务,用 then 返回 Promise 对象,串联起来
代码:
javascript
<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>
小结:
- 什么是 Promise 的链式调用?
➢ 使用 then 方法返回新 Promise 对象特性,一直串联下去 - then 回调函数中,return 的值会传给哪里?
➢ 传给 then 方法生成的新 Promise 对象 - Promise 链式调用有什么用?
➢ 解决回调函数嵌套问题
3.async 和 await 使用
3.1 async函数和await
目标:掌握async和await语法,解决回调函数地狱
定义:
async
函数是 JavaScript 的一种特殊函数,它可以在函数中使用 await
关键字。当你将一个普通函数用 async
关键字修饰后,这个函数默认返回一个 Promise
对象。普通的返回值会被封装成一个 fulfilled
状态的 Promise
对象,而抛出的任何异常都会被封装成 rejected
状态的 Promise
对象。
概念:在async函数内,使用await关键字,获取Promise对象 **"成功状态"**结果值
注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
代码:
javascript
<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_捕获错误
语法:
代码:
javascript
<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 事件循环
定义:执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环。
原因:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了 事件循环模型。
原理:
-
调用栈(Call Stack):
- 调用栈是执行代码的地方。它记录了当前正在执行的函数和函数调用的历史。当一个函数被调用时,它会被压入调用栈中,执行完成后,它会被从栈中弹出。
-
任务队列(Task Queue 或 Callback Queue):
- 任务队列用于存放异步任务的回调函数。例如,当
setTimeout
函数的定时器结束时,它的回调函数会被放到任务队列中。
- 任务队列用于存放异步任务的回调函数。例如,当
-
微任务队列(Microtask Queue):
- 微任务队列存放的是微任务(例如 Promise 的回调函数)。微任务通常比宏任务(如定时器回调)优先级更高。微任务队列的任务会在事件循环的每一轮结束之前执行完毕。
4.2 宏任务与微任务- 执行顺序
异步任务分为:
- ✓ 宏任务:由浏览器环境执行的异步代码
- ✓ 微任务:由 JS 引擎环境执行的异步代码
执行顺序:
使用图解-分析代码执行顺序
**简单理解:**同步就是会被立即执行的代码,而异步会被分配到相应的任务队列进行等待,
当栈处于空闲状态就会先清空微任务队列,再执行宏任务队列。
4.3 总结
- 什么是宏任务?
- ➢ 浏览器执行的异步代码
- ➢ 例如:JS 执行脚本事件,setTimeout/setInterval,AJAX请求完成 事件,用户交互事件等
- 什么是微任务?
- ➢ JS 引擎执行的异步代码
- ➢ 例如:Promise对象.then()的回调
- JavaScript 内代码如何执行?
- ➢ 执行第一个 script 脚本事件宏任务,里面同步代码
- ➢ 遇到 宏任务/微任务 交给宿主环境,有结果回调函数进入对应队列
- ➢ 当执行栈空闲时,清空微任务队列,再执行下一个宏任务,从1再来
5.Promise.all 静态方法
概念:合并多个 Promise 对象,等待所有 同时成功 完成(或某一个失败),做后续逻辑 。
语法:
需求:同时请求"北京","上海","广州","深圳"的天气并在网页尽可能 同时 显示
代码演示:
javascript
<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.案例 - 商品分类
商品分类
需求:尽可能同时展示所有商品分类到页面上
步骤:
-
- 获取所有的一级分类数据
-
- 遍历id,创建获取二级分类请求
-
- 合并所有二级分类Promise对象
-
- 等待同时成功,开始渲染页面
效果图:
代码:
javascript
<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 完成省市区切换效果
步骤:
-
- 设置省份数据到下拉菜单
-
- 切换省份,设置城市数据到下拉菜单,并清空地区下拉菜单
-
- 切换城市,设置地区数据到下拉菜单
页面展示:
代码:
javascript
/**
* 目标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 收集学习反馈数据,提交保存
步骤:
-
- 监听提交按钮的点击事件
-
- 依靠插件收集表单数据
-
- 基于 axios 提交保存,显示结果
页面展示:
代码:
javascript
/**
* 目标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)
})
})