《JavaScript 异步异步编程全景解析:同步 vs 异步、回调地狱突围、Promise 链式调用、async/await 与事件循环(宏微任务)深度指南》
同步代码和异步代码,回调函数地狱,Promise-链式调用,async函数和await、事件循环-eventloop、Promise.all 静态方法、案例商品分类和学习反馈
·
一、同步代码和异步代码
同步代码:
逐行执行,需原地等待结果后,才继续往下执行
异步代码:
调用后耗时,不阻塞代码继续执行,在将来完成后触发回调函数。
例如:定时器,事件,XHR...,接受异步代码的结果:回调函数
<!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">
<title>同步代码和异步代码</title>
</head>
<body>
<h2>同步代码和异步代码</h2>
<script>
// 同步和异步代码,异步执行
//----------- 1---直接输出:1,2
console.log(1)
const num = 1 + 1
console.log(num)
//---------- 2---直接输出:1,3 点击之后:2
console.log(1)
document.querySelector('h2').addEventListener('click', function() {
console.log(2)
})
console.log(3)
// ---------3---直接输出:1,3 一秒之后:2
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
// ----------4--直接输出:1,3 响应回来:2
console.log(1)
const xhr = XMLHttpRequest()
xhr.open('get', 'xxx')
xhr.addEventListener('loadend', () => {
console.log(2)
})
xhr.send()
console.log(3)
</script>
</body>
</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">
<title>回调函数地狱</title>
</head>
<body>
<h2>回调函数地狱</h2>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.3/axios.js"></script>
<script src="../lodash.js"></script>
<script>
/**
* 需求: 展示数据到下拉框中
* 1. 获取省份数据并展示第1个省
* 2. 获取第1个省的城市数据,并展示第1个城市
* 3. 获取第1个城市的区数据,并展示第1个区
* */
// 回调函数地狱: 在回调函数中调用回调函数,形成的代码结构称之为回调函数地狱
// 缺点: 可读性差,异常捕获困难
axios({
url: 'https://hmajax.itheima.net/api/province',
}).then(res => {
// console.log(res.data.list)
const pname = res.data.list[0]
document.querySelector('.province').innerHTML = pname
// ------请求省已经成功→用这个省请求市---
axios({
url: 'https://hmajax.itheima.net/api/city',
params: {
pname: pname,
}
}).then(res => {
const cname = res.data.list[0]
document.querySelector('.city').innerHTML = cname
// ------请求市已经成功→用这个市请求区县
axios({
url: 'https://hmajax.itheima.net/api/area',
params: {
pname,
cname
}
}).then(res => {
console.log(res)
document.querySelector('.area').innerHTML = res.data.list[0]
})
})
})
</script>
</body>
</html>
三、Promise-链式调用
Promise链式调用:
每一个then方法还会返回一个新生成的promise对象,这个对象可被用作链式调用
then方法的返回值:
then方法中的回调函数的返回值,会影响新生成的Promise对象最终状态和结果
作用:解决回调函数嵌套(回调函数地狱)
<!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">
<title>Promise-链式调用</title>
</head>
<body>
<h2>Promise-链式调用</h2>
<script>
/**
* Promise-链式调用
* 1. then方法会返回一个新生成的promise对象,可以被链式调用
* 2. then方法中回调函数返回值会影响新Promise对象的状态和结果
* */
function getPname() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('省份数据')
}, 2000)
})
}
function getCname() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('城市数据')
}, 2000)
})
}
// Promise链式调用的作用--解决回调函数嵌套(回调函数地狱)
// Promise链式调用: 每一个then方法还会返回一个新生成的promise对象,这个对象可被用作链式调用
// then方法的返回值: then方法中的回调函数的返回值,会影响新生成的Promise对象最终状态和结果
//前一个then里面return后一个then要用的对象
// /城市的数据要基于省份数据执行
const p = getPname()
p.then(res => {
console.log(res)
return getCname() //这里的return对象,为了的下一个then使用
}).then(res => {
// /这个then要执行,需要在前一个then里面return一个Promise对象
console.log(res)
})
</script>
</body>
</html>
1,解决回调函数地狱
核心步骤: then的回调函数中返回Promise对象
<!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">
<title>Promise-链式调用-解决回调地狱</title>
</head>
<body>
<h2>Promise-链式调用-解决回调地狱</h2>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.2/axios.min.js"></script>
<script>
/**
* 需求: 展示数据到下拉框中(Promise链式编程)
* 1. 获取省份数据并展示第1个省
* 2. 获取第1个省的城市数据,并展示第1个城市
* 3. 获取第1个城市的区数据,并展示第1个区
* */
// Promise链式调用--解决回调函数嵌套(回调函数地狱)
let pname = ''
axios({
url: 'https://hmajax.itheima.net/api/province',
}).then(res => {
// console.log(res.data.list)
pname = res.data.list[0]
document.querySelector('.province').innerHTML = pname
// ------请求省已经成功→用这个省请求市---
return axios({
url: 'https://hmajax.itheima.net/api/city',
params: {
pname: pname,
}
})
})
.then(res => {
const cname = res.data.list[0]
document.querySelector('.city').innerHTML = cname
// ------请求市已经成功→用这个市请求区县
return axios({
url: 'https://hmajax.itheima.net/api/area',
params: {
pname,
cname
}
})
})
.then(res => {
console.log(res)
document.querySelector('.area').innerHTML = res.data.list[0]
})
</script>
</body>
</html>
四、async函数和await
语法: 在async函数内,使用await关键字取代then函数,等待获取Promise对象成功状态的结果值
1,async函数和await-错误捕获
try {
// 需要被执行的语句
} catch (error) {
// error 接收错误信息
// try 有错误时执行的语句
}

<!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">
<title>async函数和await-错误捕获</title>
</head>
<body>
<h2>async函数和await-错误捕获</h2>
<form>
<span>省份:</span>
<select>
<option class="province"></option>
</select>
<span>城市:</span>
<select>
<option class="city"></option>
</select>
<span>地区:</span>
<select>
<option class="area"></option>
</select>
</form>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.3/axios.js"></script> -->
<script src="../axios.min.js"></script>
<script>
/**
* 需求: 展示数据到下拉框中(async和await)
* 1. 获取省份数据并展示第1个省
* 2. 获取第1个省的城市数据,并展示第1个城市
* 3. 获取第1个城市的区数据,并展示第1个区
* */
// 注意点1: 使用async关键字来声明函数
// async异步 是个函数 await等待
// 语法: 在async函数内,使用await关键字取代then函数,等待获取Promise对象成功状态的结果值
//async函数和await
async function func() {
try {
// 省份 await等待
const res = await axios({
url: 'https://hmajax.itheima.net/api/province',
})
const pname = res.data.list[0]
document.querySelector('.province').innerHTML = pname
//请求城市
const res1 = await axios({
url: 'https://hmajax.itheima.net/api/city',
params: {
pname: pname,
}
})
const cname = res1.data.list[0]
document.querySelector('.city').innerHTML = cname
// 请求区县
const res2 = await axios({
url: 'https://hmajax.itheima.net/api/area',
params: {
pname,
cname
}
})
console.log(res2)
document.querySelector('.area').innerHTML = res2.data.list[0]
} catch (error) {
console.log(`11`, error)
// try,catch
// 使用 async/await 关键字就可以在异步代码中使用普通的 try/catch 代码块。
try {
// 需要被执行的语句
} catch (error) {
// error接收错误信息
//try有错误时执行的语句
}
}
}
func()
console.log(2222)
</script>
</body>
</html>
五、事件循环-eventloop
JavaScript是单线程(代码逐行执行)的,
为了不让耗时代码阻塞其他代码运行,就设计了事件循环
<!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">
<title>事件循环-练习</title>
</head>
<body>
<h2>事件循环-练习</h2>
<script>
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
setTimeout(() => {
console.log(3)
}, 0)
function getProvince() {
const xhr = new XMLHttpRequest()
xhr.open('get', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
console.log(4)
})
xhr.send()
}
getProvince()
document.addEventListener('click', () => {
console.log(5)
})
console.log(6)
// 打印结果 1 6 2 3 4 点击之后打印5
</script>
</body>
</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">
<title>事件循环-经典面试题</title>
</head>
<body>
<h2>事件循环-经典面试题</h2>
<script>
console.log(1)
setTimeout(() => {
console.log(2)
const p = new Promise((resolve) => resolve(3))
p.then(res => console.log(res))
}, 0)
const p = new Promise(resolve => {
setTimeout(() => console.log(4), 0)
resolve(5)
})
p.then(res => { console.log(res) })
const p2 = new Promise(resolve => resolve(6))
p2.then(res => console.log(res))
console.log(7)
// 输出: 1 7 5 6 2 3 4
</script>
</body>
</html>
事件循环的模型(执行过程)
调用栈执行同步代码,异步代码交给宿主环境执行
异步代码等待时机成熟,送入任务队列排队(先宏任务,然后是微任务)
调用栈空闲时,反复查看并调用任务队列里的回调函数
异步任务(宏任务,微任务)
1. (宏)任务: 由浏览器环境执行的异步代码:
script标签,定时器,AJAX请求完成事件,用户交互事件等
2. 微任务: 由JS引擎环境执行的异步代码:
Promise对象.then和catch的回调
六、Promise.all 静态方法
作用:
将多个Promise对象包装成一个新的Promise对象,获取所有的成功结果,或某一个的失败原因

七、例
1,商品分类
//发一级分类的请求,可以得到一级分类的id,用一级分类的id当做查询参数,发二级分类的请求
//二级分类有4个→Promise.al1([p1,p2,p3.....])
//const res = axios()→async(函数)await---(省略了.then拿结果,如果不这样写就需要回调函数的地狱套用)
// 语法: 在async函数内,使用await关键字取代then函数,等待获取Promise对象成功状态的结果值
// 2. Promise.all 获取所有的成功结果,或者失败原因(第一个)
//async 把原来同步的一行一行按顺序执行的代码,改为异步的代码,各自执行各自的同时进行执行
//await 等待各自执行的过程都结束了之后返回结果,
// 可以不通过.then拿结果
async function fn() {
//获取一级分类的数据
const res1 = await axios({
url: 'https://hmajax.itheima.net/api/category/top',
})
console.log("yijiS", res1.data.data)
//整合二级请求对象的数组→all([])
const arr = res1.data.data.map(item => {
return axios({
url: 'https://hmajax.itheima.net/api/category/sub',
params: {
id: item.id
}
})
})
console.log('二级:', arr)
const p = Promise.all(arr)
console.log('二级p:', p)
p.then(res => {
console.log(res)
let str = res.map(item => {
const { name, children } = item.data.data
return `
<div class="item">
<h3>${name}</h3>
<ul>
${children.map(item=>{
return`
<li>
<a href="javascript:;">
<img src="${item.picture}" />
<p>${item.name}</p>
</a>
</li>
`
}).join('')}
</ul>
</div>
`
}).join('')
document.querySelector('.sub-list').innerHTML = str
})
}
fn()
2,学习反馈
需求:1. 省份列表 2. 城市列表 3. 地区列表 4. 反馈提交
下拉框select,option下拉框的选项
主要代码
// /省市区三级联动
//1.默认发请求得到省份数据-渲染页面
async function fn() {
let res1 = await axios({
url: 'https://hmajax.itheima.net/api/province',
})
// console.log(`1234567890-`, res1.data.list)
console.log(`1234567890-`, res1)
document.querySelector('.province').innerHTML = `<option value="">省份</option>` + res1.data.list.map(item => {
return `<option value="${item}">${item}</option>`
}).join('')
//2.省份的值(value)发生变化了(change),请求这个省的所有市→渲染页面
let pname = ''
document.querySelector('.province').addEventListener('change', async function() {
pname = this.value
const res2 = await axios({
url: 'https://hmajax.itheima.net/api/city',
params: {
pname
}
})
console.log(res2.data.list)
document.querySelector('.city').innerHTML = `<option value="">城市</option>` + res2.data.list.map(item => {
return `<option value="${item}">${item}</option>`
}).join('')
document.querySelector('.area').innerHTML = `<option value="">地区</option>`
})
let cname = ''
//3.市的值发生变化了,请求这个市的所有区县→渲染页面
document.querySelector('.city').addEventListener('change', async function() {
cname = this.value
const res3 = await axios({
url: 'https://hmajax.itheima.net/api/area',
params: {
pname,
cname
}
})
console.log(res3.data.list)
document.querySelector('.area').innerHTML = `<option value="">地区</option>` + res3.data.list.map(item => {
return `<option value="${item}">${item}</option>`
}).join('')
})
}
fn()
//提交数据
document.querySelector('.submit').addEventListener('click', async function() {
//收集数据
const data = serialize(document.querySelector('.info-form'), { hash: true, empty: true })
// 接口发请求提交
const res = await axios({
url: 'https://hmajax.itheima.net/api/feedback',
method: 'post',
data
})
alert(res.data.message)
})
更多推荐




所有评论(0)