zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Promise - (九)重识async&await

ampasync Promise await
2023-09-14 09:13:41 时间

第一代异步编程方案是:基于回调函数的异步编程,缺点是当实现异步串行时,会产生回调地狱

第二代异步编程方案是:基于Promise的异步编程,它使用then链式调用解决了异步任务串行时产生回调地狱的问题

那么Promise异步编程真的完美吗?

const fs = require('fs')

function read(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf-8', function(err, data) {
            if (err) return reject(err)
            resolve(data)
        })
    })
}

read('a.txt').then(data=>{
    return read(data)
}).then(data=>{
    return read(data)
}).then(data => {
    console.log(data)
})

缺点1:

then链式并不是最完美的异步串行书写方式,最完美的异步串行书写是 “异步任务之间无显示耦合关系, 异步任务间可以像同步代码一样自上而下执行”

这里then链式产生了显示耦合,且是链式执行

缺点2:

异步任务结果只能在then指定的回调函数中使用,或通过return传递给下一级then,但是无法越过下一级给下下一级使用

promise.then((data1)=>{
    return data1
}).then((data1)=>{
    // use data1
    return data2
}).then((data2)=>{    
    // 无法获取到data1
})

那么这种场景多不多呢?很多,比如:

promise封装了一个接冷水的异步任务

promise.then((水)=>{
    // 烧水
    return 热水
}).then((热水)=>{
    // 煮饭
    return 饭
}).then((饭)=>{
    // 吃饭
    // 这里我还想喝热水,发现获取不到热水
})

而针对这两个缺点,ES6又推出了Generator和Iterator以及co,他们利用Generator的执行特点实现了异步任务串行的同步化书写,利用next传参实现了异步结果,同步接收

但是由于Generator函数,yield语义化太差,以及co执行器的模板化书写太冗余,最终推出他们的语法糖async和await

async相当于function*,即生成器函数

await相当于yield,即生成器函数中的yield

以及async函数执行时,被JS隐藏的co执行器,即async = 生成器 + 执行器 + promise封装, await = yield

const fs = require('fs')

function read(path, encode) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, encode, function(err, data) {
            if (err) return reject(err)
            resolve(data)
        })
    })
}

async function fn() {
    let val1 = await read('a.txt', 'utf-8')

    let val2 = await read(val1, 'utf-8')

    let val3 = await read(val2, 'utf-8')
    console.log(val3)
}

fn()


// function* fn() {
//     let val1 = yield read('a.txt', 'utf-8')

//     let val2 = yield read(val1, 'utf-8')

//     let val3 = yield read(val2, 'utf-8')
//     console.log(val3)
// }

// function co(fn) {
//     const iterator = fn()

//     function next(value) {
//         if (!value) value = iterator.next().value
//         value.then(data => {
//             let { value, done } = iterator.next(data)
//             if (!done) {
//                 next(value)
//             }
//         })
//     }
//     next()
// }

// co(fn)

由于async,await是语法糖,所以他们底层还是基于Generator,Iterator,next,yield,co执行的,所以我们基于async和await的特性来用Generator,Iterator,next,yield,co重写一下

async函数会返回一个promise对象,promise对象的状态和结果由async函数执行的返回值决定

当return值为非promise对象时,则async函数返回一个成功态,且结果为return值的promise对象

当return值为promise对象时,则async函数返回一个新的promise对象,且状态和结果于return值的promise对象的状态和结果相同。

那么async函数的return值 对应到generator函数中是啥呢?

async函数中await对应 generator函数中的yield

async函数中return对应 generator函数中的return

而generator函数中yield和return有啥区别呢?

generator函数中yield产出的值,会被generator函数的返回值iterator迭代器对象通过调用next()获取到,且结果对象的done一定为false

generator函数中return也可以产出值,也会被generator函数的返回值iterator迭代器对象通过调用next()获取到,但是对应结果对象的done为true。

另外:

generator函数中yield可以获取下一次next传递过来的值

generator函数中return,没有下一次next,所以也就没有值给它

其次需要注意:generator函数中第一次产出值(yield或return)之前的语句是同步执行还是异步执行?

我们知道当generator函数返回的iterator调用next时,genrator函数体开始执行,执行过程中遇到yield就会暂停执行,遇到return就会结束执行,所以在第一次next后,第一次yield前的代码是同步执行的。

function* fn() {
    yield 1
    yield 3
    yield 4
    return 10
}

function co(fn) {
    const iterator = fn()

    let { value, done } = iterator.next() // 第一次next触发generator函数执行,执行遇到第一个产出值(yield或return)则暂停执行,将产出值返回
    if (done) { // 若第一次next执行后,得到结果对象的done为true,表示迭代结束,则一定遇到了return或隐式return
        return Promise.resolve(value) // 此时value是return的产出值,可能是隐式return undefined值
    }

    function next(value) {
        // 第一次之后的next,需要等待上一次next得到的结果对象的value的最终结果
        // 因为每一次next得到的结果对象中的value都可能是一个封装了异步任务的promise对象
        // 如果value不是promise对象,则也要封装为一个promsie对象,实现await等待
        value = value instanceof Promise ? value : Promise.resolve(value)
        return value.then((data) => {
            let { value, done } = iterator.next(data)
            if (!done) {
                return next(value)
            } else {
                return value
            }
        })
    }

    // 若第一次next执行后,得到的结果对象done为false,则继续next
    return next(value)
}

const res = co(fn)

console.log(res)

await右侧的表达式一般为promise对象,但也可以是其他的值

如果表达式是promise对象,await返回的是promise成功的值,如果await的promise失败了,就会抛出异常,需要通过try...catch捕获异常处理;

如果表达式是其他值,直接将此值作为await的返回值;

async函数的await对应 generator函数的yield,我们需要按照await返回值的规则,来设计yield的返回值

function* fn() {
    let v1 = yield 1
    console.log(v1)
    let v2 = yield Promise.resolve(2)
    console.log(v2)
    let v3 = yield new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK')
        }, 1000)
    })
    console.log(v3)
}

function co(fn) {
    const iterator = fn()

    let { value, done } = iterator.next()
    if (done) {
        return Promise.resolve(value)
    }

    function next(value) {
        value = value instanceof Promise ? value : Promise.resolve(value)
        return value.then((data) => {
            let { value, done } = iterator.next(data)
            if (!done) {
                return next(value)
            } else {
                return value
            }
        })
    }

    return next(value)
}

co(fn).catch(e=>{
    console.log(e)
})

这里try...catch捕获await失败promise,我是没想到如何在yield上实现,看到tj大神的co库,发现也是co(fn*).catch(),这样实现的

await关键字可以暂停async函数向下执行,知道await的promise返回结果

这个功能的实现,其实就是

yield可以暂停function*函数向下执行,直到下一次next调用,但是下次next的调用依赖于上一次next的获得的结果对象的value(promise对象)返回结果