JS中的生成器

138 阅读4分钟

生成器的介绍

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

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

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

生成器事实上是一种特殊的迭代器;

生成器函数

第一次执行

function* foo(){  
  console.log('函数开始执行~');
  const value1 = 100
  console.log(value1);
  yield     //第一个next 运行到这里结束






  //这里是第二段不执行
  console.log('第二段代码');


}

//调用生成器函数时,会给我们返回一个生成器对象 Generator
const generator = foo()

//开始执行第一段代码
console.log('--------------------------');
generator.next()

这样子就算是完整的执行了整个函数

function* foo(){  
  console.log('函数开始执行~');
  const value1 = 100
  console.log(value1);
  yield     //第一个next 运行到这里结束

  const value2 = 200
  console.log(value2);
  yield

  const value3 = 300
  console.log(value3);
  yield

  console.log('函数执行结束');


}

//调用生成器函数时,会给我们返回一个生成器对象 Generator
const generator = foo()

console.log('--------------------------');
generator.next()
generator.next()
generator.next()
generator.next()

生成器其实也是一个特殊的迭代器

//当遇到yield时候暂停函数的执行
//当遇到return 直接停止执行

function* foo(){
  console.log('函数开始执行');
  const value1 = 100
  console.log(value1);
  yield

  const value2 = 200
  console.log(value2);
  yield

  const value3 = 300
  console.log(value3);
  yield

  console.log('函数结束');
  return undefined 
}

//generator本质上是一个特殊的iterator
const generator = foo()
console.log('返回值:',generator.next() ); //返回值: { value: undefined, done: false }
console.log('返回值:',generator.next() );
console.log('返回值:',generator.next() );
console.log('返回值:',generator.next() ); //{ value: undefined, done: true }

生成器的传参

function* foo(num){
  console.log('函数开始执行');
  
  const value1 = 100 * num
  console.log('第一段代码:',value1);
  
  const n = yield value1 //value1需要通过yield 接受,这里新的参数n是下面next()传递进来的

  
  const value2 = 200 * n
  console.log('第二段代码:',value2);
  const count = yield value2
  
  const value3 = count * 0.8
  console.log(value3);
  yield value3

}

const generator = foo(3)
console.log(generator.next(10));  //这里会打印{ value: 300, done: false } ,并且函数自动执行

generator.next(5)
generator.next(30)

生成器的return 终止方法

function* foo(num){ //第一个得写在这个地方,但是我们很少用
  console.log('函数开始执行~');

  const value1 = 100 * num //第一段拿到的参数
  console.log("第一段代码:",value1)

  const n = yield value1  //在这里接受yield的参数,这是第一段的结尾,这样第二段才能用


  //generator.return(n)这个位置  return n 提前终止生成器代码
  const value2 = 200 * n
  console.log("第二段代码:",value2)
  // yield value2
  const count = yield value2

  const value3 = 300 * count
  console.log("第三段代码:",value3)
  // yield value3
  yield

  console.log('函数执行结束~');
}

const generator = foo(10)

console.log(generator.next());

//第二段代码的执行
console.log(generator.return(15));  //在第一段与第二段之间提前终止了
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

生成器的throw抛出异常方法

function* foo(){
  console.log('代码开始执行');
  
  const value1 = 100
  try{
    yield value1 
  }catch(err){
    //不能在catch里yield
    console.log('捕获到异常情况:',err);
  }
  

  console.log('第二段代码继续执行');
  const value2 = 200
  yield value2

  console.log('代码已经结束');
}

const generator = foo()

console.log(generator.next()); 

console.log(generator.throw('这是异常的信息')); //第二段代码不会执行了

使用生成器来替换迭代器

function* createNamesIterator(arr){
  //写法1
  // for(const item of arr){
  //   yield item
  // }

  //写法2
  // let index = 0
  // yield arr[index++]
  // yield arr[index++]
  // yield arr[index++]

  //写法3 yield*  加上可迭代对象,yield自动帮我们迭代
  yield* arr

}



const names = ["abc","ccc","nba"]
const namesIterator = createNamesIterator(names)

console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());

class的案例

class Classroom{
  constructor(address,name,students){
    this.address = address
    this.name = name
    this.students = students
  }

  entry(newStudent){
    this.students.push(newStudent)
  }


  *[Symbol.iterator]() {
    yield* this.students
  }
}

const classroom = new Classroom("3懂","1102",["abc","ccc"])
for(const item of classroom){
  console.log(item);
}

创建一个函数,这个函数可以迭代一个范围内的数字

function* createRangeIterator(start,end){
  // let index = start
  
  // return {
  //   next(){
  //     if(index < end){
  //       return {done:false,value:index++}
  //     }else{
  //       return {done:true,value:undefined}
  //     }
  //   }
  // }

  let index = start
  while(index < end){
    yield index++
  }
}

异步函数的处理方案

function requestData(url) {
  //异步请求的代码会被放入到executor中
  return new Promise((resolve, reject) => {
    setTimeout(() => {


      resolve(url)


    }, 2000)
  })
}

//需求:
//1> url:harry -> harry
//2> url:res + "aaa" -> res:harryaaa
//3> url:res + "bbb" => res:harrybbb

//第一种方案:多次回调
//回调地狱
// requestData("harry").then(res => {

//   requestData(res + "aaa").then(res => {
//     requestData(res + "bbb").then(res => {
//       console.log(res);   //6s 以后出来了 harryaaabbb
//                           //这是回调地狱
//     })
//   })
// })


//第二种方案:Promise中then的返回值来解决 
// requestData("harry").then(res =>{
//   return requestData(res+"aaa") //返回一个promise
// }).then(res => {
//   return requestData(res + 'bbb')
// }).then(res => {
//   console.log(res);
// })


//第三种方案:Promise + generator实现
function* getData(){
  //yield requestData("harry") 的返回值是promise
  const res1 = yield requestData("harry")
  const res2 = yield requestData(res1+"aaa")
  const res3 = yield requestData(res2+"bbb")
  console.log(res3); ////harryaaabbb
  
}
const generator = getData()
generator.next().value.then(res => {
  generator.next(res).value.then(res => {
    generator.next(res).value.then(res => {
      generator.next(res)  
    })
  })
})

//这个npm上已经有可以使用的了,包里已经帮我们实现了 叫 co
// function execGenerator(getFn){
//   const generator = getFn()
//   function exec(res){
//     const result = generator.next(res)
//     if(result.done) {
//       return result.value
//     }
//     result.value.then(res => {
//       exec(res)
//     })
//   }
//   //使用递归
//   exec()
// }

// execGenerator(getData)

//3>第三方包 co 自动执行 ,不过这个库已经很久没维护了
// TJ 大神开发的 co 和 n 都是TJ开发的  commander也是他开发的 vue cli内部也是用到了commander
//著名的express框架,koa 也是 TJ开发的  egg是基于koa的
//co大学也不是学计算机的,它是学艺术的
// const co = require('co')
// co(getData)


//4.第四种方案 async/await  在es8中已经有了关键字了

//本质上是由generator演化而来的
async function getData(){
  const res1 = await requestData("harry")
  const res2 = await requestData(res1+"aaa")
  const res3 = await requestData(res2+"bbb")
  console.log(res3);
}
getData()