async/await原理

18 阅读7分钟

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. 总结

  1. await只能在async函数中使⽤,不然会报错;
  2. async函数返回的是⼀个Promise对象,有⽆值看有⽆return值;
  3. await后⾯最好是接Promise,虽然接其他值也能达到排队效;
  4. 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⽅法执⾏后会返回⼀个对象,对象中有valuedone两个属性

  • 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来接收这个参数,注意两点

  1. 第⼀次next传参是没⽤的,只有从第⼆次开始next传参才有⽤;
  2. 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传参

根据上⽂可以知道:

  1. yield后⾯接Promise;
  2. 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了,区别在于

  1. gen函数执⾏返回值不是PromiseasyncFn执⾏返回值是Promise
  2. gen函数需要执⾏相应的操作,才能等同于asyncFn的排队效果;
  3. 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