Generator函数详解(精简版)

闲言细语:Generator函数是在ES6中引入进来,用来处理异步编程、迭代和流控制等任务。但是,一直都没有完全理解透这究竟是个什么玩意,究竟解决了什么实际的业务场景。带来了什么好处。如果你也和我一样有这个疑惑,那么接着往下看吧。

读音

Generator(gen ne 瑞 ter|/ˈdʒenəreɪtə(r)/)函数。

语法

生成器函数是一种特殊的 JavaScript 函数,生成器必须是function*。

javascript 复制代码
1、
function* name(){
}

2、
const name = function*(){
}

3、// 箭头函数不能用作generator函数
const faker = *()=>{

}

使用

这个函数不像普通函数一样立刻执行,而是返回一个特殊的对象,我们通常称之为"生成器对象"。(先不管什么是生成器对象,接着看...)每当我调用一次next()方法,它就往下执行一次,直到遇到yield关键字停止。

lua 复制代码
function* numberGenerator() {
  yield 1;
  yield 2;
  const data = yield 3;
  return data;
}

const generator = numberGenerator(); // 返回了一个生成器对象

console.log(generator.next()); 
// 输出: { value: 1, done: false } 
// 返回数据内容浅显解析 value: yield右边的值,done: 代表是否结束了
console.log(generator.next());
// 输出: { value: 2, done: false }
console.log(generator.next());
// 输出: { value: 3, done: false }
console.log(generator.next(9));
//输出: {value: 9, done: true} 为什么这里会返回9呢 因为yield的返回值,是由下一次next传递进去的。所以data=9
console.log(generator.next()); 
// 输出: { value: undefined, done: true }
// value: 没有内容,所以给了undefined,done: 已经结束了

看完这个示例,大脑中产生了一个想法,所以它有什么用呢?这个时候请看闲言细语第一句话,用来处理异步编程、迭代和流控制等任务。那直接先往异步编程的方向上靠吧。

异步编程

那根据刚刚所学习到的内容,可以写出如下代码:

ini 复制代码
function* fetchData() {
  const data1 = yield fetch('https://test.com/data1'); // yield右边返回的值是一个promise
  const data2 = yield fetch('https://test.com/data2');
  return [data1, data2];
}

// 使用生成器处理异步数据
const generator = fetchData();
generator.next().value.then(response1 => {
  generator.next(response1).value.then(response2 => {
    const result = generator.next(response2).value;
    console.log(result); // [data1, data2]
  });
});

这样写的好处是什么呢?fetchData 生成器函数用于处理两个异步数据请求。通过使用 yield,你可以逐步获取数据,而不必使用嵌套回调。我觉得还可以再抽象化一点,提取成为一个工具函数。

javascript 复制代码
function processGenerator(generator) {
  const { value, done } = generator.next();
  if (done) {
    console.log("生成器已完成");
    return;
  }

  if (value instanceof Promise) {
    value
      .then((response) => {
        console.log("Response:", response);
        processGenerator(generator); // 递归调用,继续处理下一个值
      })
      .catch((error) => {
        console.error("Error:", error);
      });
  } else {
    console.log("Value:", value);
    processGenerator(generator); // 递归调用,继续处理下一个值
  }
}

// 处理异步请求的统一方法
const handleAsyncGenerator = (generator) => {
  const _generator = generator();
  const isGenerator =
    _generator &&
    typeof _generator.next === "function" &&
    _generator.constructor &&
    _generator.constructor?.constructor?.name === "GeneratorFunction";
  if (!isGenerator) {
    return console.error("传入的参数不是Generator函数");
  }
  processGenerator(_generator);
};
// 放入需要处理的Generator函数
handleAsyncGenerator(fetchData);

有必要这么麻烦吗??? 没必要,这种场景下我直接使用async/await不就好了,一样可以控制顺序避免嵌套回调了。那么接着往下找,迭代和流控制等任务。应该就是这个了,开始找业务场景(网上有一堆场景,但我想找一个离业务更近一点的场景,这样应该能够听的懂一些)。

迭代和流控制等任务

有限状态机。(当你的业务流程需要根据状态去做某事时,不妨通过Generator函数去实现,比如:流程审批功能。再使用的时候,只需要执行一句stateMachine.next(),优雅,属实优雅)。

javascript 复制代码
function* createFiniteStateMachine() {
  let state = 'idle';

  while (true) {
    switch (state) {
      case 'idle':
        console.log('当前状态:idle');
        yield; // 等待状态转换
        state = 'processing';
        break;
      case 'processing':
        console.log('当前状态:processing');
        // 在这里执行一些操作
        yield; // 等待状态转换
        state = 'completed';
        break;
      case 'completed':
        console.log('当前状态:completed');
        // 在这里执行一些操作
        yield; // 等待状态转换
        state = 'idle';
        break;
    }
  }
}

const stateMachine = createFiniteStateMachine();

// 启动状态机
stateMachine.next();

生成器对象

方法

  1. next() 方法: 这个方法用于执行生成器函数的下一步操作,产生一个对象,该对象包含 valuedone 两个属性。value 表示生成器函数的返回值或 yield 表达式的结果,done 是一个布尔值,表示生成器是否已经完成。
  2. return() 方法: 这个方法用于终止生成器函数的执行,可以提前结束生成器函数的迭代。通常用于清理工作或资源释放。

可迭代性 可以使用 for...of 循环、Array.from()...spread 等方式迭代生成器对象的值。

javascript 复制代码
function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = myGenerator();

for (const value of generator) {
  console.log(value);
}

也可以自定义迭代方法.

迭代器协议要求返回的对象必须包含一个 next() 方法,用于迭代值和判断是否迭代完成

javascript 复制代码
const myIterable = {
  data: [1, 2, 3, 4, 5],
  index: 0,
  [Symbol.iterator]: function() {
    const self = this; // 保存对当前对象的引用
    let currentIndex = 0; // 迭代的索引

    return {
      next: function() {
        if (currentIndex < self.data.length) {
          return { value: self.data[currentIndex++] * 2, done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

// 使用自定义迭代器
for (const value of myIterable) {
  console.log(value);
}

结尾

欢迎指正~

相关推荐
叫我:松哥2 分钟前
基于机器学习的癌症数据分析与预测系统实现,有三种算法,bootstrap前端+flask
前端·python·随机森林·机器学习·数据分析·flask·bootstrap
让开,我要吃人了5 分钟前
HarmonyOS鸿蒙开发实战(5.0)网格元素拖动交换案例实践
前端·华为·程序员·移动开发·harmonyos·鸿蒙·鸿蒙开发
谢尔登13 分钟前
Webpack 和 Vite 的区别
前端·webpack·node.js
谢尔登14 分钟前
【Webpack】Tree Shaking
前端·webpack·node.js
过期的H2O230 分钟前
【H2O2|全栈】关于CSS(4)CSS基础(四)
前端·css
纳尼亚awsl43 分钟前
无限滚动组件封装(vue+vant)
前端·javascript·vue.js
八了个戒1 小时前
【TypeScript入坑】TypeScript 的复杂类型「Interface 接口、class类、Enum枚举、Generics泛型、类型断言」
开发语言·前端·javascript·面试·typescript
西瓜本瓜@1 小时前
React + React Image支持图像的各种转换,如圆形、模糊等效果吗?
前端·react.js·前端框架
黄毛火烧雪下1 小时前
React 的 useEffect 钩子,执行一些异步操作来加载基本信息
前端·chrome·react.js
蓝莓味柯基1 小时前
React——点击事件函数调用问题
前端·javascript·react.js