黑马程序员视频地址:
同步代码和异步代码
回调函数地狱与解决方法
回调函数地狱
需求:展示默认第一个省,第一个城市,第一个地区在下拉菜单中
概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
缺点:可读性差,异常无法通过一次代码捕获,耦合性严重,牵一发动全身
/**
* 目标:演示回调函数地狱
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
* 概念:在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱
* 缺点:可读性差,异常无法写一次代码获取,耦合性严重,牵一发动全身
*/
// 1. 获取默认第一个省份的名字
axios({url: 'http://hmajax.itheima.net/api/province'}).then(result => {
const pname = result.data.list[0]
document.querySelector('.province').innerHTML = pname
// 2. 获取默认第一个城市的名字
axios({url: 'http://hmajax.itheima.net/api/city', params: { pname }}).then(result => {
const cname = result.data.list[0]
document.querySelector('.city').innerHTML = cname
// 3. 获取默认第一个地区的名字
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
})
})
}).catch(error => {
console.dir(error) //无法通过一次catch捕获任意一个axios错误
})
解决方法一:Promise 链式调用
deepseek给出的解释:
Promise 链式调用:通过 Promise 状态(Pending/Fulfilled/Rejected)的不可变性,严格保证执行顺序。
Promise 的
then()
方法调用后会返回一个新的 Promise 对象。这是 Promise 链式调用的核心机制。以下是关键点解释:
1. 返回一个新的 Promise
原 Promise 不受影响:调用
then()
不会修改原始的 Promise 对象,而是生成一个新的 Promise。链式调用的基础:正是通过返回新 Promise,才能继续调用下一个
.then()
或.catch()
,形成链式结构。2. 新 Promise 的状态由回调决定
回调返回值决定状态:新 Promise 的状态(
fulfilled
或rejected
)和值,由then()
中传入的回调函数决定:
如果回调返回普通值(非 Promise),新 Promise 会以该值变为
fulfilled
。如果回调返回一个新的 Promise,新 Promise 会“跟随”这个返回的 Promise 的状态。
如果回调抛出错误,新 Promise 会变为
rejected
。博主重述:调用一次axios时,基于axios内部代码,会自动new一个promise对象,并且执行内部代码,成功会调用resolve()方法,此时原promise对象被标记为成功,然后会自动调用then方法,并且返回一个新对象,而新对象的标记与返回值有关,若返回值也为一个新promise对象,则会与其的标志保持一致,此时如果新对象执行成功(即调用resolve()方法),则会被标记为成功,从而执行then方法,如此循环,一级一级读取then方法,倘若某一级的promise对象被标记失败,则会找catch方法,即跳过其他then而直接到最后一行找catch
/**
* 目标:把回调函数嵌套代码,改成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
}).catch(error => {console.log(error)})//一次catch捕获即可获取任意某一次axios错误
解决方法二:async 和await 使用
async:标记函数为异步,使其支持 await。普通声明函数为同步函数
await:暂停异步函数,等待异步操作完成,让代码逻辑更清晰。await 会暂停当前 async 函数的执行,但不会阻塞线程。其他代码(如事件循环)可以继续运行。
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
// 1. 定义async修饰函数
async function getData() {
// 2. await等待Promise对象成功的结果,同时会返回promise对象成功执行返回的值(即then里的result)
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()
捕获错误:
/**
* 目标: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()
事件循环-EventLoop
1. 什么是事件循环?
➢ 执行代码和收集异步任务,在调用栈空闲时,反复调用任务队列里 回调函数执行机制
2. 为什么有事件循环?
➢ JavaScript 是单线程的,为了不阻塞JS 引擎,设计执行代码的模型
3. JavaScript 内代码如何执行?
➢ 执行同步代码,遇到异步代码交给宿主浏览器环境执行
➢ 异步有了结果后,把回调函数放入任务队列排队
➢ 当调用栈空闲后,反复调用任务队列里的回调函数
宏任务与微任务
Promise.all静态方法
// 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)
}).catch(error => {
console.log(error)
})
案例1—商品分类
目标:获取一级分类,并通过一级分类获取二级分类,并渲染到页面上
写法一:axios嵌套
//1. 获取所有一级分类数据
axios({
url: "https://hmajax.itheima.net/api/category/top"
}).then(result => {
const topList = result.data.data
//2. 遍历id,创建获取二级分类请求
const subList = topList.map(item => {
return axios({
url: "https://hmajax.itheima.net/api/category/sub",
params: {
id: item.id
}
})
})
//3. 合并所有二级分类Promise对象
const allList= Promise.all(subList)
//4. 等待同时成功后,渲染页面
allList.then(result => {
const data = result.map(item => {
return `
<div class="item">
<h3>${item.data.data.name}</h3>
<ul>
${item.data.data.children.map(list => {
return `
<li>
<a href="javascript:;">
<img src="${list.picture}">
<p>${list.name}</p>
</a>
</li>`
}).join("")}
</ul>
</div>
`
}).join("")
document.querySelector(".sub-list").innerHTML = data
})
})
写法二:async
async function getData()
{
try{
//1. 获取所有一级分类数据
const topList = await axios({url: "https://hmajax.itheima.net/api/category/top"})
const topListData = topList.data.data
//2. 遍历id,创建获取二级分类请求
const subList = topListData.map(subItem => {
return axios({url: "https://hmajax.itheima.net/api/category/sub", params: {id: subItem.id}})
})
//3. 合并所有二级分类Promise对象,并等待同时成功
const subListData = await Promise.all(subList)
//4.成功后渲染页面
const data = subListData.map(subData => {
return `<div class="item">
<h3>${subData.data.data.name}</h3>
<ul>
${subData.data.data.children.map(subItemData => {
return `<li>
<a href="javascript:;">
<img src="${subItemData.picture}">
<p>${subItemData.name}</p>
</a>
</li>`
}).join("")}
</ul>
</div>`
}).join("")
document.querySelector(".sub-list").innerHTML = data
}
catch(error)
{
//出错执行
}
}
getData()
案例2—学习反馈
目标一:选择省份、城市、地区数据
//1.1 设置省份下拉菜单数据
axios({url: "https://hmajax.itheima.net/api/province"}).then(provinceResult => {
const provinceList = provinceResult.data.list
//映射数据
const province = provinceList.map(province => {
return `<option value="${province}">${province}</option>`
}).join("")
//渲染到页面中
document.querySelector(".province").innerHTML = `<option value="">省份</option>` + province
})
//获取相应城市
document.querySelector(".province").addEventListener("change", e => {
//获取城市数据
axios({url: "https://hmajax.itheima.net/api/city", params: {pname: e.target.value}}).then(cityResult => {
const cityList = cityResult.data.list
const city = cityList.map(city => {
return `<option value="${city}">${city}</option>`
}).join("")
document.querySelector(".city").innerHTML = `<option value="">城市</option>` + city
})
//清除地区数据
document.querySelector(".area").innerHTML = `<option value="">地区</option>`
})
//获取相应地区
document.querySelector(".city").addEventListener("change", e => {
//获取数据
axios({url: "https://hmajax.itheima.net/api/area", params: {pname:document.querySelector('.province').value ,cname:e.target.value}}).then(areaResult => {
const areaList = areaResult.data.list
const area = areaList.map(area => {
return `<option value="${area}">${area}</option>`
}).join("")
document.querySelector(".area").innerHTML = `<option value="">地区</option>` + area
})
})
目标二:收集数据,提交数据
/**
* 目标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)
}
})