理解Generator以及async/await原理

134 阅读3分钟

相信大家在开发都使用过async和await,本文章先通过实现generator的简易原理,让大家深入了解generator。再来实现async/await原理。

1.Generator实现

理解生成器与迭代器

什么是生成器?

◼ 生成器函数也是一个函数,但是和普通的函数有一些区别:

  • 首先,生成器函数需要在function的后面加一个符号:*
  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
  • 最后,生成器函数的返回值是一个Generator(生成器-生成器事实上是一种特殊的迭代器

什么是迭代器?

  • 迭代器对象都有一个next()方法,每次调用都返回一个结果对象。
  • next()返回的对象有如下的要求
    • done(boolean)
      • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
      • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
    • value
      • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略
// 生成器函数
function* generaor() { 
  yield "aaa";
  yield "bbb";
  yield "ccc";
}

// 生成器
const gen = generaor();

// =====测试代码=====
console.log(gen.next()); // { value: 'aaa', done: false }
console.log(gen.next()); // { value: 'bbb', done: false }
console.log(gen.next()); // { value: 'ccc', done: false }
console.log(gen.next()); // { value: undefined, done: true }

原理实现

复制上面生成器函数的代码到babel官网中,转化成如下代码,看看在ES5下是如何实现Generator的:

image.png

源码中很多地方都进行封装比如:regeneratorRuntime.wrap、generaor$、_context等,所以自己实现一个简易的Generator。

function generaor$(_context) {
  while (1) {
    switch ((_context.prev = _context.next)) {
      case 0:
        _context.next = 2;
        return "aaa";

      case 2:
        _context.next = 4;
        return "bbb";

      case 4:
        _context.next = 6;
        return "ccc";

      case 6: // 迭代结束进入'end'
      case "end":
        return _context.stop();
    }
  }
}

function generaor() {
  const ctx = {
    prev: 0,
    next: 0,
    done: false,
    stop() {
      // 迭代完毕执行
      this.done = true;
    },
  };

  // 返回生成器
  return {
    next() {
      return {
        value: ctx.done ? undefined : generaor$(ctx), // 迭代结束返回undefine,没结束就进行判断
        done: ctx.done,
      };
    },
  };
}

// ======测试代码========
const gen = generaor();
console.log(gen.next()); // { value: 'aaa', done: false }
console.log(gen.next()); // { value: 'bbb', done: false }
console.log(gen.next()); // { value: 'ccc', done: false }
console.log(gen.next()); // { value: undefined, done: true }

2.async/await实现

那么我们要如何实现一个async/await呢,首先我们要知道,async/await实际上是对Generator(生成器)的封装,是一个语法糖。

下面我们通过generator函数模拟async

const axios = require("axios");

function* getUserRole() {
  const res1 = yield axios.get("http://localhost:8000/user"); // 获取用户id
  const id = res1.data.user.id;
  const res2 = yield axios.get(`http://localhost:8000/user/role/${id}`); // 获取用户role
  return res2.data.role.name;
}

const iterator = getUserRole(); // 迭代器

const { value,done } = iterator.next();
value.then((res) => {  // 等pormise拿到结果再执行next(下同)
  const { value,done } = iterator.next(res);
  value.then((res) => {
    const { value,done } = iterator.next(res);
    console.log(res); // admin
  });
});

从上面代码中可以看出,promise层层嵌套导致代码可读性极差,我们可以通过使用co函数(TJ大神写的能够使generator自动执行的函数库)对其进行异步迭代

// 异步迭代函数(co函数)
function co(iterator) {
  // 最终结果肯定返回一个promise
  return new Promise((resolve, reject) => {
    // 迭代器递归函数
    function walk(data) {
      const { value, done } = iterator.next(data); // 执行next
      if (!done) { // 如果迭代没结束,继续递归迭代
        Promise.resolve(value).then((res) => {
          walk(res);
        }, reject);
      } else { // 迭代结束直接返回
        resolve(value);
      }
    }
    walk();
  });
  
function* getUserRole() {
  const res1 = yield axios.get("http://localhost:8000/user"); // 获取用户id
  const id = res1.data.user.id;
  const res2 = yield axios.get(`http://localhost:8000/user/role/${id}`); // 获取用户role
  return res2.data.role.name;
}

// 将生成器传入异步迭代函数中,可直接自动执行,最后返回promise结果
co(getUserRole()).then((res) => {
  console.log(res); // admin
});
}

以上就是async的核心原理,主要是在于co的封装,让generator可以自动执行,直到执行结束返回promise。

总结

由于Generator在开发中不经常使用,出现不久就被async/await取代了,所以很多同学对Generator比较陌生。其实实现generator的方法有很多,本文只通过babel编辑方式实现。

其实大家最重要还是要知道async是generator和promise的语法糖就可以了

generator + yield + co异步迭代函数 => async + await