一节课彻底弄懂promise、async、await(三)完结篇

1,794 阅读6分钟

之前聊了异步编程的回调函数和promise,上一篇文章也说了,用promise解决异步编程,如果多个调用,就会看起来不那么舒服。

es6除了提供了promise还为我们提供了更加强大的async和await,async、await是Generator函数的语法糖,如果想要完全掌握async、await的用法,必须要掌握Generator函数的使用。

一、Generator 函数

1、什么是 Generator 函数?

来自阮一峰老师文档上的解释:Generator函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

你可以这么理解,这个函数自己执行不了,得让别人帮忙执行,踢一脚(next()),走一步。

基本的用法:

function* doSomething() {
    yield '吃饭'
    return '睡觉'
}

let newDoSomething = doSomething() // 自己执行不了,需要指向一个状态机

console.log(newDoSomething.next()) // {value: "吃饭", done: false}
console.log(newDoSomething.next()) // {value: "睡觉", done: true}
从上面的例子可以看出来,Generator 函数有四个特点:

1、function后面有个小*,这个地方有两种写法,没啥太大区别;

function* doSomething(){}
function *doSomething(){}

2、函数里面会有一个yield,把函数截成不同的状态;

一个yield可以截成两个状态,也就需要两个next()触发;

3、Generator函数自己不会执行,而是会返回一个遍历器对象;

4、遍历器对象会通过.next()方法依次调用各个状态。

消息传递

Generator函数除了能控制函数分状态的执行,还有一个非常重要的作用就是消息传递,还是上例子:

function *doSomething() {
    let x = yield 'hhh'
    console.log(x)
    return (x * 2)
}

let newDoSomething = doSomething()

console.log(newDoSomething.next(1))  
console.log(newDoSomething.next(2))  


打印结果:

{value: "hhh", done: false}
2
{value: 4, done: true}

具体分析一下为什么会打印这个: (重点

//{value: "hhh", done: false}
第一个next()是Generator函数的启动器
这个时候打印的是yield后面的值
重点的一句,yield后面的值并不会赋值给x

//2
暂停执行的时候,yield表达式处可以接收下一个启动它的next(...)传进来的值
你可以理解为:
这个时候第二个next传入的参数会把第一个yield的值替换掉

 //{value: 4, done: true}
这个时候,x被赋值2,所以打印2*2
注意几个问题:
第一个next()是用来启动Generator函数的,传值会被忽略,没用
yield的用法和return比较像,你可以当做return来用,如果yield后没值,return undefined
最后一个next()函数,得到的是函数return的值,如果没有,也是undefined
彻底理解了上面的概念,再来看下下面的栗子:
function *doSomething() {
    let x = yield 'hhh'
    let y = yield (x + 3)
    let z = yield (y * 3)
    return (x * 2)
}

let newDoSomething = doSomething()

console.log(newDoSomething.next(1))  // {value: "hhh", done: false}
console.log(newDoSomething.next(2))  // {value: 5, done: false}
console.log(newDoSomething.next(100)) // {value: 300, done: false}
console.log(newDoSomething.next(1000)) // {value: 4, done: true}

还是用上面的思路分析一下:

第一个next(1),传进去的值没用,打印的是yield后的值
第二个next(2),这个时候的x已经被赋值为2,所以打印2+3
第三个next(100),这个时候的y已经被赋值为100,所以打印100*3
第四个next(1000),这个时候y已经被赋值为1000,,但是打印的是x*2,所以打印的4 
再来看个特殊的情况:(特殊的才是容易掉坑的)
function *doSomething() {
    let x = yield 'hhh'
    console.log(x)
    let y = yield (x + 3)
    console.log(y)
    let z = yield (y * 3)
    return (x * 2)
}

let newDoSomething = doSomething()

console.log(newDoSomething.next(1))
console.log(newDoSomething.next(2))
console.log(newDoSomething.next())
console.log(newDoSomething.next())

看下打印结果:

{value: "hhh", done: false}
2
{value: 5, done: false}
undefined
{value: NaN, done: false}
{value: 4, done: true}

分析下为什么打印undefined?

1、第一个next(1)传进去的1,没有起任何意义,打印的{value: "hhh", done: false};
2、第二个next(2)传进去的2,所以x会打印2,第二个next(2)打印2+3;
3、第三个next()传进去的是空,那么y打印的就是未定义,undefined*3那肯定就是NaN;
4、第四个next()传进去的是空,但是return的是x,刚才说了x是2,那打印的是2*2

二、async、await

1、什么是async、await?

async、await是Generator函数的语法糖,原理是通过Generator函数加自动执行器来实现的,这就使得async、await跟普通函数一样了,不用再一直next执行了。

他吸收了Generator函数的优点,可以通过await来把函数分状态执行,但是又不用一直next,可以自动执行。

还是上例子:

栗子1
function f() {
    return new Promise(resolve =>{
        resolve('hhh')
    })
}
async function doSomething1(){
    let x = await f()
}

doSomething1()

打印结果:

hhh

看了上面的例子,可以看出async有三个特点:

1、函数前面会加一个async修饰符,来证明这个函数是一个异步函数;

2、await 是个运算符,用于组成表达式,它会阻塞后面的代码

3、await 如果等到的是 Promise 对象,则得到其 resolve值。
栗子2
async function doSomething1(){
    let x = await 'hhh'
    return x
}

console.log(doSomething1())

doSomething1().then(res => {
    console.log(res)
})

打印结果:

Promise {<pending>}
hhh

分析一下上面的栗子可以得到这两个特点:

1、async返回的是一个promise对象,函数内部 return 返回的值,会成为 then 方法回调函数的参数;

2、await如果等到的不是promise对象,就得到一个表达式的运算结果。

async、await项目中的使用

现在有一个封装好的,获取数据的方法,我们分别用promise、Generator、async来实现发请求,做一下比较:

function getList() {
    return new Promise((resolve, reject) =>{
        $axios('/pt/getList').then(res => {
            resolve(res)
        }, err => {
            reject(err)
        })
    })
}

promise

function initTable() {
    getList().then(res => {
        console.log(res)
    }).catch(err => {
        this.$message(err) // element的语法
    })
}

然后直接调用就可以 这么做看起来非常的简洁,但是如果多个请求调用 就会是.then,.then看起来非常不舒服

Generator函数

function *initTable(args) {
    const getList = yield getlist(args)
    return getList
}

function getList() {
    const g = initTable(this.searchParams)
    const gg = g.next().value
    gg.then(res =>{
        this.total = res.data.count
        if (res.data.list) {
          this.tableList = res.data.list
          this.tableList.forEach(e => {
            e.receiveAmt = format(e.receiveAmt)
          })
        } else {
          this.tableList = []
        }
    })
}

这个看起来就比较伤,写起来非常麻烦

async await

async initTable() { // table列表查
  const getData = await getList(this.searchParams)
  return getData
},

getList() {
  this.initTable().then(res =>{
    this.tableList = res.data.list
  })
}

这样写好像也很简单,而且非常方便

主要是如果调用多个接口,可以直接多个await

总结

因为一个好朋友的一道面试题,突然有了写一下关于同步、异步编程的一篇文章,写起来才发现涉及的知识点太杂,就一共写了三篇文章:

1、关于什么同步、异步,其中涉及了一些堆栈和消息队列、事件轮询的知识;

mp.weixin.qq.com/s/LAwfxg0TK…

2、关于异步编程的几个解决方案,主要是回调函数和promise;

mp.weixin.qq.com/s/z7b5jzRd1…

3、关于异步编程的终极解决方案Generator函数以及他的语法糖async、await。

个人的微信公众号:小Jerry有话说,平时会发一些技术文章和读书笔记,欢迎交流。