理解async&await
# async、await是做什么的
首先阅读在红宝书上的概念是,async和await是ES8规范新增的语法,这个特性从行为和语法上都增强了 Javascript,能够以同步的方式写的代码能够异步执行。 虽然promise为我们很好地解决了回调地狱的问题,但是在有些需求场景下,还是会存在多层嵌套的情况。 比如如下情况:
function request(item) {
return new Promise(resolve => {
setTimeout(()=>{
resolve(item+'res')
},1000)
})
}
// 以下模拟先请求一个接口后请求另一个接口
request('req1').then(r1=>{
console.log(r1) //1s后 req1res
request('req2').then(r2=>{
console.log(r2) // 2s后 req2res
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面的情况使用async和await是如下写法
async function fn(){
const res1 = await request('req1')
console.log(res1) //1s后 req1res
const res2 = await request('req2')
console.log(res2) //2s后 req2res
}
fn()
2
3
4
5
6
7
# await
可以看出 使用async和await可以良好解决嵌套层级的问题,await后面跟着的那句代码相当于resolve()那里的代码 再下面的代码相当于then里面的代码 await会等待后面那句代码执行完之后再向下执行之后的代码 需要注意的是 通常我们在使用await时,await any(就是await后面跟着的)中的any一般都是异步操作,但是如果any是同步操作,可以理解成any被Promise.resolve(any)这样处理过,因此它也会被放到微任务队列。 例如:
function request(item) {
setTimeout(()=>{
console.log(item+'res')
},1000)
}
async function fn(){
await request('req1')
await request('req2')
}
fn() // 同时输出req1res,req2res
2
3
4
5
6
7
8
9
10
需要注意的是,只有在async定义的函数中才可以使用await 想完全理解await,我们还需要知道要它并非只是等待一个值可用那么简单。
- JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。
- 等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。
- 因此,即使 await 后面跟着一个立即可用的值,
函数的其余部分
也会被异步求值。 如下是在JS高程中的例子:
async function fn(){
console.log(2)
await null //(在 foo()中)await 关键字暂停执行,为立即可用的值 null 向消息队列中添加一个任务
console.log(4)
}
console.log(1)
fn()
console.log(3) // 同步线程的代码执行完成后,JavaScript从消息队列中取出任务,来恢复异步函数的执行
// 输出 1 2 3 4
2
3
4
5
6
7
8
9
10
# async
我们可能会好奇,为什么只有使用async定义的函数才能实现异步同步形式,首先可以看一下async的返回值
async function fn(item){ return item+'res' }
console.log(fn) //async ƒ fn(item){ return item+'res' }
console.log(fn('req')) //Promise {<fulfilled>: 'reqres'}
fn('req').then((res)=>{
console.log(res) // reqres
})
2
3
4
5
6
7
- 可以看出async的返回值是一个
fulfilled状态
的Promise,且在赋予async方法返回值之后可以使用then方法来输出 - async函数如果使用return关键字返回了值(如果没有return会返undefined),这个返回值会
被包装成一个期约对象
,并且async函数始终返回的是Promise对象 - 使用 async 关键字可以让函数具有异步特征,但
总体上其代码仍然是同步求值
的。而在参数或闭包方面,async函数仍然具有普通 JavaScript 函数的正常行为。 - async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。毕竟,异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有什么区别
那么async/await到底是怎么实现的的呢? 在了解async/await实现原理之前,需要先了解generator函数
# generator
# 基本用法
生成器的形式是一种函数且要在函数前面加上一个 *
来表示这是一个生成器 只要是可以定义函数的地方就可以定义生成器 (箭头函数不能定义生成器函数)
- 在生成器函数中有一个
yield
关键字,通过yield可以让生成器停止和开始执行 - 当生成器执行时遇到
yield
,遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用 **next()**方法来恢复执行 - next方法的执行会返回一个对象 包括
value
和done
两个属性
function* gen() {
yield 1
yield 2
yield 3
return 4
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: 4, done: true } 如果没有返回值那么value为undefined
2
3
4
5
6
7
8
9
10
11
12
- 如果后面是函数
function fn(n){
console.log(n)
return n+1
}
function* gen(){
yield fn(0)
yield fn(1)
return 3
}
const ge = gen()
console.log(ge.next()) // 0 {value: 1, done: false}
console.log(ge.next()) // 1 {value: 2, done: false}
console.log(ge.next()) // 2 {value: 3, done: true}
2
3
4
5
6
7
8
9
10
11
12
13
- 如果是Promise
function fn(n){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(n+1)
},1000)
})
}
function* gen(){
yield fn(0)
yield fn(1)
return 3
}
const ge = gen()
console.log(ge.next()) // {value: Promise, done: false}
console.log(ge.next()) // {value: Promise, done: false}
console.log(ge.next()) // {value: 3, done: true}
//获取promise的结果
ge.next().value.then(res1=>{
console.log(res1) //1s后输出 1
ge.next().value.then(res2=>{
console.log(res2) //2s后输出 2
console.log(ge.next()) //2s后输出 {value: 3, done: true}
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 使用next传参
generator函数能通过
next
传参 通过yield
来接受参数
function* gen() {
const num1 = yield
console.log(num1)
const num2 = yield 1
console.log(num2)
return 2
}
const ge = gen()
console.log(ge.next()) // {value: undefined,done:false}
console.log(ge.next(111)) // 111 {value: 1,done: false}
console.log(ge.next(222)) // 222 {value: 2,done: true}
2
3
4
5
6
7
8
9
10
11
- 使用Promise且next传参
function fn(num){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(num*2)
},1000)
})
}
function * gen(){
const num1 = yield fn(1)
const num2 = yield fn(3)
const num3 = yield fn(6)
return num3
}
const ge = gen()
const next1 = ge.next() //第一次执行next()
next1.value.then(res=>{
console.log(next1) // 一秒后输出 {value: Promise{<fullfilled>:2}, done: false}
console.log(res) // 一秒后输出 2
const next2= ge.next(res)
next2.value.then(res2=>{
console.log(next2) // 两秒后输出 {value: Promise{<fullfilled>:6},done:false}
console.log(res2) // 两秒后输出 6
const next3 = ge.next(res2)
next3.value.then(res3=>{
console.log(next3) //三秒后输出 {value: Promise{<fullfilled:12>},done:false}
console.log(res3) //三秒后输出 12
console.log(ge.next(res3)) //三秒后输出{value: 12, done: true}
})
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# CO
实际上在generator函数中使用Promise和next传参的模式 已经接近async/await的方式了,但是generator需要通过调用next()方法执行yield,但是async不需要,这其中就有CO的帮助,CO是由TJ大神编写的JavaScript异步解决方案的库,CO实际上就是一个generator自执行器,通过递归的方式自动处理每一步yield。 所以大概就是 generator+Promise+CO=>async/await Promise让代码扁平化,CO让代码同步化 举🌰:
function fn(num){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(num*2)
},1000)
})
}
function * gen(){
const num1 = yield fn(1)
console.log(num1)
const num2 = yield fn(2)
console.log(num2)
return num2
}
//常规写法
let ge = gen()
let {value} = ge.next()
value.then(res1=>{
console.log(res1) //2
const next2 = ge.next(res1)
next2.value.then(res2=>{
console.log(res2)//4
console.log(ge.next(res2))
})
})
//CO写法
co(gen()).then(res=>{
console.log(res); //4
//CO会帮助我们自执行
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30