JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,意味着程序的执行是逐行执行的但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环。
只要碰到耗时操作,都将其作为异步任务,交给宿主环境去执行,不会阻塞线程,执行完后会放在任务队列中等待调用栈全部执行完毕后再循环拿出执行。
在JavaScript中,所有的任务都可以分为同步任务和异步任务
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等;调用后耗时,不阻塞代码继续执行(不必原地等待),在将来完成后触发一个回调函数
代码分享:
<!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>同步代码和异步代码</title>
</head>
<body>
<button class="btn">点我</button>
<script>
// 目标:阅读并回答代码执行和打印的顺序
// 运行页面 2s 内点击: 1432
// 运行页面 2s 后点击: 1423
const result = 0 + 1
console.log(result)
setTimeout(() => {
console.log(2)
}, 2000)
document.querySelector('.btn').addEventListener('click', () => {
console.log(3)
})
document.body.style.backgroundColor = 'green'
console.log(4)
</script>
</body>
</html>
二、回调函数地狱
回调函数地域:在回调函数一直向下嵌套回调函数,一直嵌套下去就形成回调函数地狱
问题:(1)可读性差 (2)异常捕获困难 (3)耦合性严重
三、Promise - 链式调用
概念:依靠 then() 方法会返回一个新生成的 Promise 对象特性,继续串联下一环任务,直到结束
细节:then() 回调函数中的返回值,会影响新生成的 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>Promise_链式调用</title>
</head>
<body>
<script>
/**
* 目标:掌握 Promise 的链式调用
* 需求:把省市的嵌套结构,改成链式调用的线性结构
*/
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// 获取到省份
resolve('湖北省')
}, 500)
})
const p2 = p.then(res => {
console.log(res)
// Promise 的 .then 回调函数中可以继续返回一个 Promise 对象
// 在 .then 回调函数中返回的 Promise 对象结果会传递给下一个 .then
// 形成链式调用
return new Promise((resolve, reject) => {
setTimeout(() => {
// 获取到省份 + 城市
resolve(res + '武汉市')
}, 500)
})
})
// console.log(p2 === p)
const p3 = p2.then(res => {
console.log(res)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + '黄陂区')
}, 500)
})
})
p3.then(res => {
console.log(res)
})
</script>
</body>
</html>
效果展示:
代码展示2:
<!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>Promise链式调用_解决回调地狱</title>
</head>
<body>
<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.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:把回调函数嵌套代码,改成Promise链式调用结构
* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中
*/
// 不能使用 try--catch 捕获异常
// 因为 try--catch 只能捕获同步代码的异常, promise 的异常可以使用 .catch 方法来捕获
let pname
axios({ url: 'http://hmajax.itheima.net/api/province' })
.then(res => {
// console.log(res)
pname = res.data.list[0]
document.querySelector('.province').innerHTML = pname
// 获取到省份信息后, 继续发请求获取城市信息, 将这个 Promise 对象返回出去
return axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
})
.then(res => {
// 继续 .then 获取到城市信息
// console.log(res)
const cname = res.data.list[0]
document.querySelector('.city').innerHTML = cname
// 发请求获取区县信息, 将这个 Promise 对象返回出去
return axios({ url: 'http://hmajax.itheima.net/api/area', params: { cname, pname } })
})
.then(res => {
// console.log(res)
document.querySelector('.area').innerHTML = res.data.list[0]
})
.catch(e => {
console.log(e)
})
</script>
</body>
</html>
四、async 和 await 使用
定义:async 函数是使用 async 关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。 async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise 。
概念: 在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象成功状态的结果值
4.2 async函数和await - 捕获错误
代码展示:
<!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>async函数和await_解决回调函数地狱</title>
</head>
<body>
<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.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
/**
* 目标:掌握async和await语法,解决回调函数地狱
* 概念:在async函数内,使用await关键字,获取Promise对象"成功状态"结果值
* 注意:await必须用在async修饰的函数内(await会阻止"异步函数内"代码继续执行,原地等待结果)
*/
// async 关键字只能用在函数前, 给函数进行修饰
// await 必须结合 async 使用
async function getInfo() {
// await 获取的返回值就是 .then 回调函数中的第一个参数
// axios({ url: 'http://hmajax.itheima.net/api/province' }).then(res => {
// })
try {
const res = await axios({ url: 'http://hmajax.itheima.net/api/province' })
// console.log(res.data.list[0])
const pname = res.data.list[0]
document.querySelector('.province').innerHTML = pname
const cityRes = await axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })
// console.log(cityRes)
const cname = cityRes.data.list[0]
document.querySelector('.city').innerHTML = cname
const areaRes = await axios({url: 'http://hmajax.itheima.net/api/area', params: { pname, cname }})
document.querySelector('.area').innerHTML = areaRes.data.list[0]
} catch (e) {
console.log(e)
}
}
// 总结: await 的代码只是看起来是同步的, 本质还是异步代码, 可以理解为 await 返回值及以后的代码都是 .then 回调函数中的代码, 它只是帮我们优化了开发的语法, 让我们的代码看起来更加优雅
getInfo()
</script>
</body>
</html>
总结:
await 的代码只是看起来是同步的, 本质还是异步代码, 可以理解为 await 返回值及以后的代码都是 .then 回调函数中的代码, 它只是帮我们优化了开发的语法, 让我们的代码看起来更加优雅。
async 是异步的意思,await则可以理解为 async wait。所以可以理解async就是用来声明一个异步方法,而 await是用来等待异步方法执行
async函数返回一个promise对象,
正常情况下,await命令后面是一个 Promise对象,返回该对象的结果。如果不是 Promise对象,就直接返回对应的值
不管await后面跟着的是什么,await都会阻塞后面的代码
五、事件循环-EventLoop
定义:JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。
概念:JavaScript 单线程(某一刻只能执行一行代码),为了让耗时代码不阻塞其他代码运行,设计了事件循环模型
执行代码和收集异步任务的模型,在调用栈空闲,反复调用任务队列里回调函数的执行机制,就叫事件循环
5.2 宏任务与微任务
ES6 之后引入了 Promise 对象, 让 JS 引擎也可以发起异步任务
异步任务分为:
宏任务:由浏览器环境执行的异步代码
微任务:由 JS 引擎环境执行的异步代码
Promise 本身是同步的,而then和catch回调函数是异步的
案例展示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>事件循环_练习</title>
</head>
<body>
<script>
/**
* 目标:阅读并回答执行的顺序结果
* 1 5 3 2 4
*/
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
function myFn() {
console.log(3)
}
function ajaxFn() {
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://hmajax.itheima.net/api/province')
xhr.addEventListener('loadend', () => {
console.log(4)
})
xhr.send()
}
for (let i = 0; i < 1; i++) {
console.log(5)
}
ajaxFn()
document.addEventListener('click', () => {
console.log(6)
})
myFn()
</script>
</body>
</html>
案例展示2:
<!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>微任务与宏任务</title>
</head>
<body>
<script>
/**
* 目标:阅读并回答打印的执行顺序
* 1 4 3 2
*/
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)
</script>
</body>
</html>
案例展示3:
<!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>事件循环经典_经典面试题</title>
</head>
<body>
<script>
// 目标:回答代码执行顺序
// 1 7 5 6 2 3 4
console.log(1)
setTimeout(() => {
console.log(2)
const p = new Promise(resolve => resolve(3))
p.then(result => console.log(result))
}, 0)
const p = new Promise(resolve => {
setTimeout(() => {
console.log(4)
}, 0)
resolve(5)
})
p.then(result => console.log(result))
const p2 = new Promise(resolve => resolve(6))
p2.then(result => console.log(result))
console.log(7)
</script>
</body>
</html>
六、Promise.all 静态方法
概念:合并多个 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>Promise的all方法</title>
</head>
<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
*/
const p1 = axios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: '110100'
}
})
const p2 = axios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: '310100'
}
})
const p3 = axios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: '440100'
}
})
const p4 = axios({
url: 'http://hmajax.itheima.net/api/weather',
params: {
city: '440300'
}
})
Promise.all([p1, p2, p3, p4]).then(res => {
console.log(res);
// const str = res.map(item => {
// const city = item.data.data.area
// const weather = item.data.data.weather
// return `<li>${city}---${weather}</li>`
// }).join('')
const str = res.map(({ data: {data:{ area, weather }}}) => `<li>${city}---${weather}</li>`).join('')
document.querySelector('.my-ul').innerHTML = str
})
</script>
</body>
</html>