01-async await语法
我们先来学习下语法
<script>
async function 函数名() {
const result = await Promise对象
// 拿到Promise对象内成功的结果继续向下执行
}
</script>
示例
写个小例子, 来使用下async+await
<script>
// 目标: 掌握下async和await语法
// 目的: 用await取代then函数, 来提取成功的值在原地
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功的值')
}, 2000)
})
// 普通函数: async+await
async function fn() {
const result = await p
console.log(result)
}
fn()
// 箭头函数: async+await
const myFn = async () => {
const result = await p
console.log(result)
}
myFn()
</script>
小结
-
async和await的作用是什么?
-
取代then函数, 来提取成功的结果
02.概念async和await注意事项
目标
了解async和await的注意事项
讲解
-
新建html文件, 来讲解如下注意事项
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(1)
reject(new Error('失败'))
}, 2000)
})
async function myFn() {
try {
const res = await p
console.log(res);
} catch (err) {
console.error(err)
}
}
myFn()
</script>
小结
-
await后面的值都可以是什么?
-
可以是Promise对象或者一个基础类型的值, 但常配合Promise对象使用, 提取其成功的结果
-
await想要捕获Promise内失败结果怎么做?
-
使用try和catch代码块进行捕获
03.概念_EventLoop事件循环
目标
理解EventLoop事件循环的概念
讲解
-
说明:JavaScript 是一门单线程执行的脚本语言。也就是说,同一时间只能做一件事情。
-
思考:单线程执行任务队列会有什么问题呢? 如果前一个任务非常耗时,则后续的任务就不得不一直等待。 为了防止某个耗时任务导致程序假死的问题,异步代码 由 js 委托给宿主环境(node环境, 浏览器)等待执行 JS引擎(c++写的代码)用来阅读+解析+执行, 我们编写的代码
-
然后异步任务代码执行后, 要执行的回调函数会在任务队列中排队等待
-
组成:
-
执行栈(JS主线程/引擎)
-
宿主环境
-
任务队列
-
-
图示:
-
图示2:
-
阅读如下代码, 更好的理解事件循环
console.log(1); const userName = '老李' if (true) { console.log(2); } else { console.log(3); } setTimeout(() => { console.log(4); }, 1000) function myFn() { console.log(5); } for (let i = 0; i < 3; i++) { console.log(6); } myFn() setTimeout(() => { console.log(7); }, 0) const youFn = () => { console.log(8); } document.addEventListener('click', () => { console.log(9); }) const obj = { name: '老刘' } youFn() console.log(10);
-
事件循环的比较简单,它是一个在 "JavaScript 引擎等待任务","执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的无限循环。 引擎的一般算法:
-
当有任务时:
-
从最先进入的任务开始执行。
-
-
没有其他任务,休眠直到出现任务,然后转到第 1 步。
-
-
任务队列 根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源。 在事件循环中,每进行一次循环的关键步骤如下:
-
在此次循环中选择最先进入队列的任务(oldest task),如果有则执行(一次)
-
检查是否存在 微任务(Microtasks),如果存在则不停地执行,直至清空 微任务队列(Microtasks Queue)
-
更新 render(DOM渲染)
-
以上为一次循环,主线程重复执行上述步骤
-
在上述循环的基础上需要了解几点:
-
JS分为同步任务和异步任务
-
同步任务都在主线程上执行,形成一个执行栈
-
主线程之外,宿主环境管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
-
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
小结
-
什么是事件循环?
-
主线程逐行执行代码, 遇到异步任务, 交给宿主环境执行, 执行完毕把要执行的回调函数推入任务队列, 等待主线程空闲的时候, 会调用任务队列中回调函数推入主线程执行栈执行, 执行后再次尝试清空任务队列里下一个回调函数, 这样不断清空和执行任务队列里回调函数的过程就叫事件循环
04.概念_微任务和宏任务
目标
-
了解微任务和宏任务的概念
-
掌握微任务队列和宏任务队列的执行时机
讲解
-
在上节课我们学到的事件循环中, 有一个任务队列, 任务队列可以细分为2个队列
-
微任务队列
-
宏任务队列
-
你也可以认为, 任务队列, 类似一个数组结构, 里面都是异步代码要放入执行栈执行的, 回调函数体
-
哪些是微任务代码, 哪些是宏任务代码呢?
微任务代码
宿主环境是JS引擎
任务(代码) | 微任务 | 环境 |
---|---|---|
Promise.then() | 微任务 | 浏览器 |
宏任务代码
宿主环境是浏览器
任务(代码) | 宏任务 | 环境 |
---|---|---|
script | 宏任务 | 浏览器 |
事件 | 宏任务 | 浏览器 |
网络请求(Ajax) | 宏任务 | 浏览器 |
setTimeout() 定时器/setInterval() 多次定时器 | 宏任务 | 浏览器 |
-
重点注意
-
异步任务的回调函数, 才会放入, 推入任务队列中, 等待被执行栈调用
-
微任务在宏任务之前被执行(不包含最开始执行的script标签(宏任务))
-
-
阅读如下代码执行结果
console.log(1); setTimeout(() => { console.log(2); }, 0) let p = new Promise((resolve, reject) => { resolve(3) }) p.then(res => { console.log(res); }) console.log(4);
小结
-
任务队列可以细分成哪2大类?
-
宏任务队列和微任务队列
05.概念微任务和宏任务执行顺序
目标
-
掌握同步任务, 微任务, 宏任务的执行顺序
讲解
-
先执行第一个宏任务(一般是script标签内/模块内)从头开始, 执行所有同步任务
-
中间遇到微任务和宏任务, 交给宿主环境, 然后有结果, 暂时把回调函数, 推入到相应任务队列中等待
-
同步代码执行完毕后, 尝试先清空当前微任务队列中所有回调函数, 一个个调入执行栈执行
-
然后再执行宏任务队列中的一个宏任务, 执行宏任务里所有同步代码, 回到步骤1的循环继续
图示:
-
阅读如下代码回答结果
console.log(1) setTimeout(function() { console.log(2) new Promise(function(resolve) { console.log(3) resolve() }).then(function() { console.log(4) }) }) new Promise(function(resolve) { console.log(5) resolve() }).then(function() { console.log(6) }) setTimeout(function() { console.log(7) new Promise(function(resolve) { console.log(8) resolve() }).then(function() { console.log(9) }) }) console.log(10)
小结
-
微任务和宏任务以及同步代码是如何执行的?
-
先执行第一个宏任务, 然后同步任务, 然后微任务, 然后下一个宏任务如此循环, 直到所有代码执行完毕