1. async/await
1.1. 介绍
async/await的⽤处:⽤同步⽅式,执⾏异步操作
function request(num) { // 模拟接⼝请求
return new Promise(resolve => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
request(1).then(res1 => {
console.log(res1) // 1秒后 输出 2
request(2).then(res2 => {
console.log(res2) // 2秒后 输出 4
})
})
现在有⼀个新的要求:先请求完接⼝1,再拿接⼝1返回的数据,去当做接⼝2的请求参数,那我们也可以这么做:
request(5).then(res1 => {
console.log(res1) // 1秒后 输出 10
request(res1).then(res2 => {
console.log(res2) // 2秒后 输出 20
})
})
如果嵌套的多了,这个时候就可以⽤async/await来解决了:
async function fn () {
const res1 = await request(5)
const res2 = await request(res1)
console.log(res2) // 2秒后输出 20
}
fn()
使⽤async/await代替上述的内容:
async function fn () {
await request(1)
await request(2)
// 2秒后执⾏完
}
fn()
async function fn () {
const res1 = await request(5)
const res2 = await request(res1)
console.log(res2) // 2秒后输出 20
}
fn()
在async函数中,await规定了异步操作只能⼀个⼀个排队执⾏,从⽽达到⽤同步⽅式,执⾏异步操作的效果。
注意:await只能在async函数中使⽤
刚刚上⾯的例⼦await后⾯都是跟着异步操作Promise,那如果不接Promise?
function request(num) { // 去掉Promise
setTimeout(() => {
console.log(num * 2)
}, 1000)
}
async function fn() {
await request(1) // 2
await request(2) // 4
// 1秒后执⾏完 同时输出
}
fn()
可以看出,如果await后⾯接的不是Promise的话,其实是达不到类似同步的效果的
Q:什么是async?async是⼀个位于function之前的前缀,只有async函数中,才能使⽤await。那async执⾏完是返回是什么?
async function fn () {}
console.log(fn) // [AsyncFunction: fn]
console.log(fn()) // Promise {<fulfilled>: undefined}
可以看出,async函数执⾏完会⾃动返回⼀个状态为fulfilled的Promise,也就是成功状态,但是值却是undefined,那要怎么才能使值不是undefined呢?只要函数有return返回值就⾏了。
async function fn (num) {
return num
}
console.log(fn) // [AsyncFunction: fn]
console.log(fn(10)) // Promise {<fulfilled>: 10}
fn(10).then(res => console.log(res)) // 10
1.1.1. 总结
- await只能在async函数中使⽤,不然会报错;
- async函数返回的是⼀个Promise对象,有⽆值看有⽆return值;
- await后⾯最好是接Promise,虽然接其他值也能达到排队效;
- async/await作⽤是⽤同步⽅式,执⾏异步操作
1.1.2. 语法糖
Q:async/await是⼀种语法糖,那么什么是语法糖呢?
A:语法糖是简化代码的⼀种⽅式,⽤其他⽅式也能达到同样的效果,但写法可能没有这么便利。
ES6的class也是语法糖,因为其实⽤普通function也能实现同样效果
回归正题,async/await是⼀种语法糖,⽤到的是ES6⾥的迭代函数——generator函数
2. generator
2.1 介绍
generator
函数跟普通函数在写法上的区别就是,多了⼀个星号*
,并且只有在generator
函数中才能使⽤yield
,⽽yield
相当于generator
函数执⾏的中途暂停点,⽐如下⽅有3个暂停点。⽽怎么才能暂停后继续⾛呢?那就得使⽤到next
⽅法,next
⽅法执⾏后会返回⼀个对象,对象中有value
和 done
两个属性
- value:暂停点后⾯接的值,也就是yield后⾯接的值;
- done:是否generator函数已⾛完,没⾛完为false,⾛完为true;
function* gen() {
yield 1
yield 2
yield 3
}
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: undefined, done: true }
可以看到最后⼀个是undefined,这取决于你generator函数有⽆返回值
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 }
2.1.1. yield后接函数
yield
后⾯接函数的话,到了对应暂停点yield
,会⻢上执⾏此函数,并且该函数的执⾏返回值,会被当做此暂停点对象的value
function fn(num) {
console.log(num)
return num
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
// 1
// { value: 1, done: false }
console.log(g.next())
// 2
// { value: 2, done: false }
console.log(g.next())
// { value: 3, done: true }
2.1.2. yield后接promise
function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }
如果想获取的是两个Promise的结果1 和 2,可以使⽤Promise的then
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
console.log(res1) // 1秒后输出 1
const next2 = g.next()
next2.value.then(res2 => {
console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
console.log(res2) // 2秒后输出 2
console.log(g.next()) // 2秒后输出 { value: 3, done: true }
})
})
2.1.3. next函数传参
generator
函数可以⽤next
⽅法来传参,并且可以通过yield
来接收这个参数,注意两点
- 第⼀次
next
传参是没⽤的,只有从第⼆次开始next
传参才有⽤; next
传值时,要记住顺序是,先右边yield
,后左边接收参数;
function* gen() {
const num1 = yield 1
console.log(num1)
const num2 = yield 2
console.log(num2)
return 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next(11111))
// 11111
// { value: 2, done: false }
console.log(g.next(22222))
// 22222
// { value: 3, done: true }
2.1.4. Promise&next传参
根据上⽂可以知道:
yield
后⾯接Promise;next
函数传参; 所以⼀起使⽤时的效果为:
function fn(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传⼊上次的res1
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false}
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传⼊上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传⼊上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})
2.2. 实现async/await
上⽅的generator
函数的Promise
&next
传参,就很像async/await
了,区别在于
- gen函数执⾏返回值不是
Promise
,asyncFn
执⾏返回值是Promise
; - gen函数需要执⾏相应的操作,才能等同于
asyncFn
的排队效果; - gen函数执⾏的操作是不完善的,因为并不确定有⼏个
yield
,不确定会嵌套⼏次;
针对这种情况,可以通过⾼阶函数(HOC)封装:
⾼阶函数:参数是函数,返回值也可以是函数。
function highorderFn(函数) {
// ⼀系列处理
return 函数
}
根据上述代码,可以封装⼀个⾼阶函数
,接收⼀个generator函数
,并经过处理,返回⼀个具有async
函数功能的函数:
function generatorToAsync(generatorFn) {
// 经过⼀系列处理
return 具有async函数功能的函数
}
2.2.1. 返回值promise
function* gen() {
}
const asyncFn = generatorToAsync(gen)
console.log(asyncFn()) // 期望这⾥输出 Promise
这⾥需要针对 generatorToAsync 进⾏处理:
function* gen() {
}
function generatorToAsync (generatorFn) {
return function () {
return new Promise((resolve, reject) => {
})
}
}
const asyncFn = generatorToAsync(gen)
console.log(asyncFn()) // Promise
2.2.2. 结合上述代码
把之前的处理代码,加⼊generatorToAsync函数中
function fn(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
function generatorToAsync(generatorFn) {
return function () {
return new Promise((resolve, reject) => {
const g = generatorFn()
const next1 = g.next()
next1.value.then(res1 => {
const next2 = g.next(res1) // 传⼊上次的res1
next2.value.then(res2 => {
const next3 = g.next(res2) // 传⼊上次的res2
next3.value.then(res3 => {
// 传⼊上次的res3
resolve(g.next(res3).value)
})
})
})
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res)) // 3秒后输出 8
到这⾥,就已经实现了async/await的初始功能了
async function asyncFn() {
const num1 = await fn(1)
const num2 = await fn(num1)
const num3 = await fn(num2)
return num3
}
asyncFn().then(res => console.log(res)) // 3秒后输出 8
2.2.3. 结合多个await⽅法
因为async中可以⽀持若⼲个await,await的个数是不确定的。同样类⽐,generator函数中,也可能有2个yield,3个yield,5个yield,所以需要对上述代码进⾏改造
function generatorToAsync(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments) // gen有可能传参
// 返回⼀个Promise
return new Promise((resolve, reject) => {
function go(key, arg) {
let res
try {
res = gen[key](arg) // 这⾥有可能会执⾏返回reject状态的Promise
} catch (error) {
return reject(error) // 报错的话会⾛catch,直接reject
}
// 解构获得value和done
const { value, done } = res
if (done) {
// 如果done为true,说明⾛完了,进⾏resolve(value)
return resolve(value)
} else {
// 如果done为false,说明没⾛完,还得继续⾛
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
}
}
go("next") // 第⼀次执⾏
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))
2.2.4. 测试结果
- async/await
async function asyncFn() {
const num1 = await fn(1)
console.log(num1) // 2
const num2 = await fn(num1)
console.log(num2) // 4
const num3 = await fn(num2)
console.log(num3) // 8
return num3
}
const asyncRes = asyncFn()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
- generatorToAsync
function* gen() {
const num1 = yield fn(1)
console.log(num1) // 2
const num2 = yield fn(num1)
console.log(num2) // 4
const num3 = yield fn(num2)
console.log(num3) // 8
return num3
}
const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8