前言
前端开发中常常使用async-await
来替换.then() .catch()
的写法,我们也知道只有await
到了想要的值才会接着往下执行。async-await
允许你以一种更同步的方式 编写异步代码
同步就同步,异步就异步,"以一种更同步的方式编写异步代码"是什么玩意儿?不扯淡呢嘛
为了更好的理解async-await
的作用,就不得不先来了解一下JS
的事件循环模型:
JS
是单线程的,主线程中的任务在调用栈中从上至下依次执行(同步)。但是为了防止线程阻塞(比如遇到定时器),会借助浏览器实现异步 的效果。当异步条件满足之后(如计时时间到了),会将任务放入任务队列中,等主线程内的东西执行完了,再挨个取出来执行。但是,异步任务的任务队列又可以分为宏任务队列和微任务队列。微任务队列中的任务会比宏任务队列中的任务先执行
宏任务和微任务:
宏任务: setTimeout setInterval
等属于宏任务, 上一个宏任务执行完, 才会考虑执行下一个宏任务
微任务: promise
的 .then .catch
的需要执行的内容, 属于微任务, 满足条件的微任务, 会比宏任务先执行(也可以理解成插在宏任务队列的最前面执行)
再次强调,调用栈(主线程)中的代码从上至下执行,遇到异步时又分为宏任务和微任务
事件循环队列:
直接看下面这段代码:
javascript
console.log(1) // 主线程任务
setTimeout(function() {
console.log(2) // 宏任务1
}, 0)
const p = new Promise((resolve, reject) => {
resolve(1000) // 主线程任务
})
p.then(data => {
console.log(data) // 微任务
})
console.log(3) // 主线程任务
// 1 3 1000 2
对于这道题,首先有个坑。new Promise
这行代码属于主线程的任务,只不过它把 promise
状态从 pedding
设置成 fullfield
,而不是打印
p.then
就是微任务,所以它会进入微任务循环队列,会在宏任务队列之前执行
async-await 的认知:
在前端开发中,常见场景是在 AJAX
请求时,我们会有诸如下面这种写法:
javascript
const getData = async () => {
const res = await axios.get('xxx')
console.log(res)
}
getData()
async/await
是建立在Promise
对象基础上的语法糖,它允许你以一种更同步的方式 编写异步代码。一个async
函数返回一个Promise
对象,而await
关键字则用于等待 一个Promise
的结果
说白了就是借助 async-await,让我们的这段代码看起来更像是:代码走到第二行,会等 axios 返回结果给 res 了再往下执行。这听起来更像是同步的表述,可 AJAX 我们都知道它是异步的,难道冲突了?
先假设我们不使用 async-await
我们会怎么写?我们会写成.then .catch
的形式:
typescript
axios.get('xxx')
.then((data)=>{
const res = data
console.log(res)
})
.catch(()=>{
xxx
})
这段代码就好理解了,axios.get
是调用栈(主线程)任务,then() catch()
回调是微任务
观察上述两段代码,你会发现,await
等待 Promise
的结果之后 执行的内容其实就是.then
里面的回调。换个说法,await
拿到结果 之后执行的是微任务,而请求结果的过程(也就是)axios.get('xxx')
仍然是主线程就执行的任务
注意,此处一直在强调是拿到结果之后
搞明白了这一点,其实前面那个看似冲突的问题也就解决了
javascript
const getData = async () => {
const res = await axios.get('xxx')
console.log(res)
}
getData()
首先执行getData()
,然后发现有个axios.get('xxx')
主线程任务,所以执行它;而等待返回结果、console.log(res)
会放入微任务队列;等主线程执行完毕,再来执行它
所以async-await看起来像同步,是因为在执行完主线程后执行微任务
几道题目:
- 例题一
scss
async function fn () {
console.log(111)
}
fn()
console.log(222)
// 111 222
不要一看见 async
就觉得有异步,像这里它都没出现 await
,所以其实和普通函数一样
- 例题二
javascript
async function fn () {
console.log('fn start')
const res = await fn2()
console.log(res) // 微任务
}
async function fn2 () {
console.log('fn2 start')
}
fn()
console.log('test')
// fn start -> fn2 start -> test -> undefined
走到第九行,开始执行 fn()
的内容;打印fn start
;往下发现有个 fn2()
,执行 fn2()
的内容;后面等待 fn2()
返回结果,以及 console.log(res)
属于微任务,进入微任务队列;此时第九行代码执行完毕;但调用栈任务还没完,还有一个第十行,所以打印 test
;打印完 test
,页面栈任务算是执行完毕;去任务队列找有没有要执行的东西,发现微任务队列还有个 console.log(res)
没执行,但由于fn2()
并没有返回东西,所以打印 undefined
- 例题三
javascript
async function async1() {
console.log('async1 start')
await async2()
console.log('asnyc1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeOut')
}, 0)
async1()
new Promise(function (reslove) {
console.log('promise1')
reslove();
}).then(function () {
console.log('promise2')
})
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// asnyc1 end
// promise2
// setTimeOut
思路:先执行调用栈任务 -> 执行完毕看看微任务队列有没有任务 -> 执行完看看宏任务队列有没有任务
走到第 9 行,打印script start
;第十行是异步,交给浏览器,之后满足条件进入宏 任务队列;走到第 13 行,进入 async1()
;打印async1 start
;走到第 3 行,进入 async2()
,打印async2
;然后等待 async2()
的结果和打印asnyc1 end
进入微 任务队列;自此执行完了第 13 行的代码;接着往下走;到第 15 行打印promise1
;then()
回调进入微 任务队列;接着打印script end
;自此调用栈任务全部执行完毕
由于微任务有两个任务,按照先后顺序,依次打印asnyc1 end
、promise2
微任务也执行完了,从宏任务队列拿任务执行,也就是打印setTimeOut