likes
comments
collection
share

不看后悔,async和await详解

作者站长头像
站长
· 阅读数 40

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')
    

    不看后悔,async和await详解

  • 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')

运行结果为:不看后悔,async和await详解

结论:如果await 右边是一个函数,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将async剩余任务推入微任务队列

不看后悔,async和await详解 不看后悔,async和await详解

**这是因为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 undefinedasync2()函数执行完毕,await async2() 转化为 Promise.resolve(undefined),因此将cl(2) 推入微任务队列

所以真正的结果是:1 7 6 2 4 3

不看后悔,async和await详解

案例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。

不看后悔,async和await详解

原理:学习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())

不看后悔,async和await详解

可以看到最后一个是undefined,这取决于你generator函数有无返回值

yield + 函数

yield后面接函数,如果到了对应暂停点,会立刻执行该函数,且该函数的返回值,会作为此yield返回对象的value值

不看后悔,async和await详解不看后悔,async和await详解

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结果是 12,其实也很简单,它返回的对象.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())

不看后悔,async和await详解

组合起来的效果

            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很像了,但是区别在于

  1. gen函数返回值不是promise,async函数返回值是promise
  2. 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
评论
请登录