~/static/img/default-avatar-0.jpeg

yinanchen

前端开发工程师

技术文档

揭秘 async await语法糖

yinanchen 2022-05-30

一、JS异步解决方案

  • 回调函数地狱
  • Promise
  • genrator函数 ----async await
  • stream流模式----Rxjs

二、有什么用?怎么用?

async await 其实是用同步的方式解决,解决异步的操作,举个例子

eg. 先请求接口1后用接口1的数据去请求接口2

// request => Promise

request(1).then(res1 => {
  console.log(res1)
  request(res1).then(res2 => {
    console.log(res2)
  })
})

//如果多个接口嵌套,就像重回回调地狱一样

用async await是什么样?

async function fn() {
    const res1 = await request(1)
    const res2 = await request(res1)
    console.log(res2)
}

代码的执行逻辑是怎么样的呢?很简单,跟平常js同步代码执行一致

  • 先执行request(1)await等待请求结束拿到结果res1
  • 等待拿到res1结果后再执行request(res1)等待请求结束拿到res2

其实async await 的执行就有点像排队,在async函数中,await关键字规定异步操作只能一个一个的排队执行,从而达到用同步的方式,执行异步的操作

三、async await背后的实现原理

async await是基于generator函数的语法糖

(一)、iterator(遍历器,ES6新特性)

  1. 什么是iterator?

javascript表示集合的数据结构

  • 数组Array

  • 对象Object

  • Map

  • Set

    需要一种统一的接口机制,来处理不同的数据结构----iterator诞生

    Iterator遍历器是一种接口,为不同的数据结构提供统一的访问机制;任何数据结构只要部署了iterator接口,就可以完成遍历的操作

  1. iterator的遍历过程

    Js原生具备Iterator接口的数据结构有以下:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

这些数据结构的原型中有一个[Symbol.iterator]属性,是一个函数,函数执行返回的是一个Iterator

Iterator中有一个next方法,调用这个方法可以将Iterator的指针指向下一个遍历元素,并且得到返回值,

返回值是一个包裹着value属性(数据结构遍历的元素值)和一个done属性(表示当前遍历的状态,布尔值)

Iterator其实本质就是一个有着一个next(一个指针函数)属性的对象,举个示例

const iterator = [1,2][Symbol.iterator]() // 返回一个iterator

iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: undefined, done: true}

(二)、Generator函数

  1. 什么是协程

协程,意思就是多个线程互相协作,完成异步任务

  1. 协程有点像函数,又有点像线程。它的运行流程大致如下。

    1. 第一步,协程A开始执行。
    2. 第二步,协程A执行到一半,进入暂停,执行权转移到协程B
    3. 第三步,(一段时间后)协程B交还执行权。
    4. 第四步,协程A恢复执行。
  2. 什么是Generator函数?

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

从形式上讲,generator函数就是一个普通函数,但是有2个特征

  1. function关键字与函数名之间有一个星号
  2. 函数体内部使用yield表达式,定义不同的内部状态
  3. generator函数的执行
function* gen() {
  yield 1
  return 2
}
const g = gen() // generator函数, g是一个iterator遍历器

g.next() // {value: 1, done:false}
g.next() // {value: 2, done:true}

generator函数的执行不会像普通函数执行得到函数内部return的的结构,generator函数的执行会返回一个指向内部状态的指针对象,也就是上面所介绍的Iterator遍历器对象

gennerator函数执行后,必须调用遍历器的next方法,才能使指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止

  1. 遍历器对象的next方法的运行逻辑如下。
    1. (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    2. (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
    3. (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    4. (4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,有点类似js里面的惰性求值了(lazy)

  1. next的参数
function* gen() {
  const a = yield 1
  console.log(a)
  return 2
}
const g = gen()

g.next() // {value: 1, done:false}
g.next() // 先打印 undefined 后打印{value: 2, done:true}

想要yield 1拥有返回值,该怎么做?

next函数的参数就作为yield表达式的返回值

function* gen() {
  const a = yield 1
  console.log(a)
  return 2
}
const g = gen()

g.next() // {value: 1, done:false}
g.next(6) // 先打印 6 后打印{value: 2, done:true}
  1. yield 语句后面接promise
const fn = (num) => {
   return new Promise(resolve => {
      resolve(num)
  })
}

function* gen() {
  yield fn(1)
  return 3
}
const g = gen()
const next1 = g.next() // {value: Promise { <pending> }, done: false}

next1.value.then((res1) => {
  console.log(res1)
})

(三)、async await的简易实现

基于上方的generator函数yield 语句后接Promise和next传参,就很像async/await了,区别在于

  • gen函数执行返回值不是Promise,asyncFn执行返回值是Promise
  • gen函数需要执行相应的操作,才能等同于asyncFn的排队效果
  • gen函数执行的操作是不完善的,因为并不确定有几个yield,不确定会嵌套几次
  1. 解决第一个问题

return 一个Promise即可

function asyncFn() {
  return function() {
    return new Promise((resolve,reject) => {})
  }
}
  1. 解决第二个问题,依次将generator函数调用next后的value值,也就是promise执行then之后把结果当做下个next函数的参数
function asyncFn(gen) {
  return function() {
    return new Promise((resolve,reject) => {
      const g = gen()
      g.next().value.then(res => {
        g.next(res)
      })
    })
  }
}
  1. 解决第三个问题,不确定几个yield,用递归函数
function asyncFn(gen) {
  return function () {
    return new Promise((resolve) => {
      const g = gen()
      function next(res) {
        const { value, done } = g.next(res)
        if (done) {
          return resolve(value)
        } else {
          return value.then((val) => next(val))
        }
      }
      next()
    })
  }
}
作者相关知识精选
  • chrome 106版本之后,支持对某个容器的媒体查询:

    .a {
       container-type: inline-size;
    }
    
    .b {
       color: yellow;
    }
    
    @container (min-width: 400px) {
      .b {
         color: blue;
      }
    }
    
    
    查看更多
  • 除了通过eval函数还有另一种方式可以将字符串当初js代码运行 new Function('data','return data')(data) ` 签名第一个是传参,后面的字符串是代码

    查看更多
  • 一个忽略的点

    svg文件不能以文件路径的形式放置在 img标签的src里面,这样是不会显示出来的

    因为svg的真正链接路径应该是svg里面的 xlink:href的路径,通常是以data:..., base64....开头的地址

    查看更多