likes
comments
collection
share

异步的终极方案Async-Await 和Generator

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

Async-Await

基本介绍

之前解决异步我们一直使用Promise.then()方案,虽然解决了回调地狱的情况,但使用链式写法也并不特别优雅。比如看下面的代码。 异步的终极方案Async-Await 和Generator

所以就出现了一种号称异步的终极方案AsyncAwait。我们看他的定义

async 函数是使用async关键字声明的函数。async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。

我们可以把代码改写成下面这样: 异步的终极方案Async-Await 和Generator 通过上面的改写,我们可以使用同步的写法来实现异步编程。 需要注意的是,如果Promise是被reject的,需要通过try catch来捕获,代码如下所示: 异步的终极方案Async-Await 和Generator

注意事项

同一个async函数中的await前面的await先执行完成之后再执行后面的await,但不同async函数中的await是不会互相阻塞的。比如常见的就是forEach,我们看下面代码:

  const promise1 = () =>  new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const promise2 = () =>  new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 1000)
  })

  const promise3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 1000)
  })

  const promiseList = [promise1, promise2, promise3];

  // 打印结果不符合预期,三个值在等待1s后同时打印出来了并没有依次执行
  promiseList.forEach(async promiseItem => {
    const val = await promiseItem()
    console.log(val);
  })

执行效果如下所示: 异步的终极方案Async-Await 和Generator 执行效果不符合预期的原因就是forEach中的三个await在三个不同的async函数中,是相互独立的,并不会await 我们将代码改成如下方式:

const promise1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const promise2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 1000)
  })

  const promise3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 1000)
  })

  const promiseList = [promise1, promise2, promise3];


  // 输出符合预期
  // 先打印promise1 val等1s后打印promise2 val再等1s后打印promise3 val
  const fn = async () => {
    for (let i = 0; i < promiseList.length; i++) {
      // for循环的三个await 都在同一个async函数中
      // 所以第二个await 会等第一个await,第三个await会等第二个await依次执行
      const val = await promiseList[i]()
      console.log(val);
    }
  }
  fn()

执行效果如下所示: 异步的终极方案Async-Await 和Generator 打印效果符合预期,因为在这里三个await是在同一个async函数中的,所以会依次执行每一个await

Async的实现原理 - Generator

基本介绍

上面提到async 函数是 AsyncFunction 构造函数的实例async function* 声明定义了一个异步生成器函数,其返回一个 AsyncGenerator 对象。 比如说我们一般在代码中有三个方法分别获取一些后台的数据,如果我们使用async函数的话,一般这么写:

  const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })
 const promiseFn = async () => {
    const val1 = await getAjaxData1()
    console.log('接受到的值1', val1)
    const val2 = await getAjaxData2()
    console.log('接受到的值2', val2)
    const val3 = await getAjaxData3()
    console.log('接受到的值3', val3)
 }
promiseFn()

效果如下所示: 异步的终极方案Async-Await 和Generator 其实async是会使用function * 生成一个函数异步生成器。如下如所示:而每一个yield后面的逻辑,都需要生成器函数调用next()方法执行,其返回值有两部分组成

  1. done:表当前函数生成器是否执行完,false表未执行完,true表执行完
  2. value:每个yield后面的返回值(在这里就是返回一个promise对象) 异步的终极方案Async-Await 和Generator 实际上通过function *获取promise的值完整逻辑是这样:
const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })

  // generate执行每一个yield获取数据
  function* getAjaxDataStep() {
    yield getAjaxData1()
    yield getAjaxData2()
    yield getAjaxData3()
  }

  // 先执行 function* 一次,得到迭代器
  // promise实例通过.then方法获取promise的结果
  const myGenerate = getAjaxDataStep()
  myGenerate.next().value.then(val => {
    console.log('接受到的值1', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值2', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值3', val)
    return myGenerate.next().value
  }).then(val => {
    console.log('接受到的值4', val)
    return myGenerate.next().value
  })

其执行效果如下: 异步的终极方案Async-Await 和Generator 可以看到和上面使用Promise执行得到的效果一样。

实现一个自动执行的Generator

我们如果不使用上面async await或者是Promise 对象.then()的方式,我们也可以封装一个函数,自动执行function *中的每一个任务,来获取每个值

const getAjaxData1 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise1 val'), 1000)
  })

  const getAjaxData2 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise2 val'), 2000)
  })

  const getAjaxData3 = () => new Promise((resolve, reject) => {
    setTimeout(() => resolve('promise3 val'), 3000)
  })

  // generate执行每一个yield获取数据
  function* getAjaxDataStep() {
    yield getAjaxData1()
    yield getAjaxData2()
    yield getAjaxData3()
  }

  // 一个高阶函数,接受function * 为入参
  const autoRunGenerateFn = (genFn) => {
    // 得先执行一次,得到迭代函数
    const myGen = genFn()
    let count = 1;
    // 递归执行该函数
    // 如果done: false就已知执行autoRun
    // 递归出口就是done: true
    const autoRun = () => {
      const result = myGen.next()
      const done = result.done
      const value = result.value
      if (done) {
        // 是否已近完成:完成递归出口
        console.log('已完成:', value);
      } else {
        // 是否已近完成:未完成 
        if (value instanceof Promise) {
          // promise的值
          value.then(v => {
            // promise的值通过.then获取
            console.log(`接受到的值${count}`, v)
            count++
            // 未全部完成,在promise.then内再次autoRun
            autoRun()
          })
        } else {
          // 非promise其值可直接获取
          console.log(`接受到的值${count}`, v)
          count++
          // 未全部完成,再次autoRun
          autoRun()
        }
      }
    }
    autoRun()
  }

  autoRunGenerateFn(getAjaxDataStep)

实现效果如下所示: 异步的终极方案Async-Await 和Generator

参考资料

Async function *

转载自:https://juejin.cn/post/7255588784590110776
评论
请登录