不看后悔,async和await详解
async await详解
基础:
async声明该函数是异步的,且该函数会返回一个promise。
await必须放在async函数中使用
-
await+Promise
这是最常见的场景,await 会等待Promise的状态改为
fullfilled
,如果成功,那么会将async函数剩余任务推入到微任务队列,如果失败,那么剩余任务不会被推入微任务队列执行,它会返回Promise.reject(err) -
await + 普通的值
**即使await右边非函数,只是一个普通的数值,但它本质上是将其转化为
Promise.resolve(2)
,所以会返回一个成功的promise
**因此,当await等待到了成功的结果后,它会将async函数剩余内容推入到微任务队列中等待执行。
async function run() { console.log('start 1') const res = await 2 console.log(res) console.log('end') } run() console.log('3')
-
await+函数
一个最简单的场景:
function fn() {
console.log('fn start')
console.log('fn end')
}
async function run() {
console.log('start 1')
const res = await fn()
console.log(res)
console.log('end')
}
run()
console.log('3')
运行结果为:
结论:如果await 右边是一个
函数
,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将async剩余任务推入微任务队列
**这是因为await等待的永远是promise,如果函数返回undefined,那么await会等待函数执行完成
,将返回值转化为Promise.resolve(undefined)
**(即第二种情况 await + 普通值)。如果函数执行完成后,返回一个失败的promise,那么它将不会再把剩余任务推入到微任务队列。
复杂点的案例,如果fn函数里面嵌套了await
案例:
async function async1() {
console.log(1)
await async2()
console.log(2)
}
const async2 = async () => {
await setTimeout((_) => {
Promise.resolve().then((_) => {
console.log(3)
})
console.log(4)
}, 0)
}
const async3 = async () => {
Promise.resolve().then(() => {
console.log(6)
})
}
async1()
console.log(7)
async3()
分析:
-
首先调用async1 输出1 ,await async2()立刻调用async2(),直至async2函数完成后,才会将cl(2)推入微任务队列
-
调用async2(), await 定时器,(定时器本身也是个函数),因此先将定时器的回调函数推入宏任务队列,定时器本身返回一个定时器ID
因此,async2可以转化为:
async ()=>{ await 数值; //即第二种情况 }
重点:await 数值会转化为await Promise.resolve(数值),再将
async函数中剩余任务推入到微任务队列执行
。这时候,async2函数中的剩余任务还有个return undefined
,这代表async2函数并不能立刻执行完毕
,会将return undefined推入到微任务队列中
(这才代表着async2函数真正执行结束
)
目前:宏任务队列:定时器回调函数任务
微任务队列:return undefined(async2函数执行完毕)
- 回到开始,await async2(),目前async2还没有执行结束,因此调用cl(7)
- 调用async3(),微任务队列推入 cl(6)
目前:宏任务队列:定时器回调函数任务
微任务队列:return undefined(async2函数执行完毕) cl(6)
- 从微任务队列中取出第一个任务,return undefined,
async2()函数执行完毕,await async2()
转化为 Promise.resolve(undefined),因此将cl(2) 推入微任务队列
所以真正的结果是:1 7 6 2 4 3
案例2:
async function async1() {
console.log(1)
await async2()
console.log(2)
}
const async2 = async () => {
await (async () => {
await (() => {
console.log(3)
})()
console.log(4)
})()
}
const async3 = async () => {
Promise.resolve().then(() => {
console.log(6)
})
}
async1()
console.log(7)
async3()
思路:
-
首先跟开始一样,调用async1() 输出1,遇到await async2(),进入等待状态,等待async2()函数完成
-
调用async2函数,遇到
await 立即执行函数1
,立即执行立即执行函数1,又遇到了await 立即执行函数2
,继续执行立即执行函数2,输出3。同时立即执行函数2没有返回值,等同于return undefined。因此可以转化为await Promise.resolve(undefined)
等待到了成功的promise,将
cl(4)和立即执行函数1的返回值结果(return undefined)即立即执行函数1完成 推入到微任务队列
。
目前:宏任务队列:
微任务队列:cl(4) return undefined(即立即执行函数1完成)
-
因为async2函数内部
await 立即执行函数1
,所以它需要等待立即执行函数1完成
后,将async2函数执行完成推入微任务队列,再将cl(2)推入微任务队列 -
输出7
-
调用async3 ,将cl(6)推入微任务队列
目前:宏任务队列:
微任务队列:cl(4) return undefined(即立即执行函数1完成) cl(6)
- 同步任务完成,调用微任务队列任务,输出4,立即执行函数1完成,将async2完成推入微任务队列,将cl(2)推入微任务队列。
目前:宏任务队列:
微任务队列: cl(6) async2完成 cl(2)
所以最后的结果是:1 3 7 4 6 2。
原理:学习sunshine_Lin大佬,加入个人理解
以上的几个案例搞懂了,足够解决面试中的async和await的笔试题,接下来是原理部分。
async、await
本质上是 一种语法糖
,它是基于 generator生成器函数实现的
generator
-
生成器函数有个特点,就是函数名前会多个
*
,只有在generator函数中,才可以调用yield
,且生成器函数会返回一个可迭代的迭代器
function* gen() { yield 1 yield 2 yield 3 }
yield + 普通数值
yield
:可以把其理解为暂停点,当生成器函数执行时,遇到 yield
就会中断执行,只有迭代器调用 next()
方法,才会继续执行.
next方法
执行后会返回一个对象,对象中有value 和 done
两个属性。
function* gen() {
yield 1
yield 2
yield 3
}
const iterator = gen()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
可以看到最后一个是undefined,这取决于你generator函数有无返回值
yield + 函数
yield后面接函数,如果到了对应
暂停点
,会立刻执行该函数
,且该函数的返回值,会作为此yield返回对象的value值
yield + promise
前面说了,函数执行返回值会当做暂停点对象的value值,那么下面例子就可以理解了,前两个的value都是pending状态的Promise对象(因为是异步的关系,但是点开看,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
,其实也很简单,它返回的对象.value.then()
即可以获取到成功或失败的结果
function fn(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
let value1 = g.next()
value1.value.then((v) => {
console.log(v)
})
next函数传参
generator返回的迭代器,每次调用next()方法
,还可以传递参数
,参数会作为yield的返回值
注意:
第一次next传参是无效的
,只有第二次next传参才有效- next传参时,先执行yield右边,它会修改返回的对象的value值,然后把
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))
console.log(g.next(22222))
console.log(g.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 'xxx'
}
const iterator = gen()
let next1 = iterator.next() //返回一个1s后改变状态的promise
next1.value.then((v) => {
console.log(v) //1s后输出2
let next2 = iterator.next(v) //next传参,num1的值即为v,调用fn函数,返回一个1s后改变状态的promise
next2.value.then((v2) => {
console.log(v2) //再过1s后输出4
let next3 = iterator.next(v2) //num2的值为v2 4
next3.value.then((v3) => {
console.log(v3)
let finalNext = iterator.next()
console.log(finalNext)
})
})
})
这就跟async和await很像了,但是区别在于
- gen函数返回值不是promise,async函数返回值是promise
- gen函数需要进行一系列的next操作,async函数不需要
我们可以封装一个高阶函数。什么是高阶函数
呢?高阶函数
的特点是:参数是函数,返回值也可以是函数。下方的highorderFn
就是一个高阶函数
function highorderFn(函数) {
// 一系列处理
return 函数
}
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) {
//返回一个具有async函数功能的函数
return function () {
return new Promise((resolve, reject) => {
const iterator = generatorFn()
const next1 = iterator.next()
next1.value.then((v) => {
console.log(v) //1s后输出2
let next2 = iterator.next(v) //next传参,num1的值即为v,调用fn函数,返回一个1s后改变状态的promise
next2.value.then((v2) => {
console.log(v2) //再过1s后输出4
let next3 = iterator.next(v2) //num2的值为v2 4
next3.value.then((v3) => {
console.log(v3) //再过1s后输出8
let finalNext = iterator.next(v3) //将v3传递给num3
resolve(finalNext.value)
})
})
})
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then((v) => {
console.log(v) //3s后输出8
})
至此,其实就实现了async和await的效果了
最终版本
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
//即调用gen.next(arg),并传入参数
} 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有可能是成功或者失败
//注意,如果传入的promise,那么它会简单的直接返回该promise对象
return Promise.resolve(value).then(
(val) => go('next', val), //如果还没结束,就再次调用go方法,同时传递结果值
(err) => go('throw', err),
)
}
}
go('next') // 第一次执行
})
}
}
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
转载自:https://juejin.cn/post/7282603015750713381