JavaScript异步解决方案之Generator

375 阅读4分钟

概念

Generator,生成器。
一个generator function(生成器函数)返回的是一个生成器对象。

语法

代码

// 声明一个generator函数
function* helloWorldGenerator(){
  console.log('1')
	yield 'hello'
  console.log('2')
  yield 'world'
  return 'ending'
}
// 使用 Generator函数生成一个生成器对象
var g = helloWorldGenerator()
// 调用next方法
g.next()
// { value: 'hello', done: false }

g.next()
// { value: 'world', done: false }

g.next()
// { value: 'ending', done: true }

g.next()
// { value: undefined, done: true }

理解

  1. 从形式上,Generator函数是一个普通函数。但是有两个特征。
  2. function关键字和函数名之间有个 * 号
  3. 函数体内使用了yield(产出)关键字
  4. 从语法上,可以理解成 Generator是一个状态机,封装了多个内部状态(有疑问)
  5. 执行Generator函数 会返回一个遍历器对象,可以使用for..of循环,遍历出Genertor函数内的每一个状态。

上面代码中,定义了一个Generator函数,该函数内部有三个状态: hello ,world和return语句( 结束执行)

调用

Generator函数的调用方法和普通函数一样,也是在函数名后面加上一对圆括号。
但是不同的是,Generator函数调用之后,并不会马上执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象,也就是遍历器对象。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或者上一次停下来的地方开始执行。直到遇到下一个yield表达式或者return语句为止。
Generator函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

next()方法

调用next方法返回一个对象,包含两个属性,一个是value属性,表示yield后表达式的值,一个是done属性,值为true或者false,表示是否还有下一个状态,生成器函数是否执行完毕。
遇到yield语句,就停下来,去计算yield表达式之后的值,当计算完毕之后,将结果赋值给value属性。
第一次调用,Generator函数开始执行,直到遇到第一个yield表达式为值。

yield 表达式

由于Generator函数返回的是一个遍历器对象。只有调用next方法才会遍历下一个内部状态。
遍历器对象的next方法的运行逻辑如下:

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

** 
如果Generator函数没有return 语句,那么Generator最后一次执行,返回的应该是 { value:'undefined',done:'true'}
如果Generator函数有return 语句,那么Generator最后一次执行,返回的应该是 { value:value,done:'true'},这里的value指的是 return语句的值。
**

next方法的参数

yield表达式本身没有返回值,next方法可以带一个参数,该参数就会作为上一个yield表达式的返回值。

作为异步编程解决方案

Generator函数是ES6提供的一种异步编程解决方案,最大的改变,是可以让我们以类似同步的方式编写异步的代码。

为什么

Generator函数可以暂定执行和恢复执行, 这是它能封装异步任务的根本原因。
除此之外,它还有两个特征,使它可以作为异步编程的完美解决方案。

  • 函数体内外的数据传递
  • 错误处理机制

函数体内外数据交换

  1. 向外输出  next方法的返回值的value属性,是Generator函数向外输出数据
  2. next方法会接受参数,向Generator函数体内输入输入数据。
// 声明Generator函数
function* gen(x){
	var y = yield x+2
  return y
}
// 生成器对象
var g = gen()
// 第一次调用next方法
g.next()  // { value: 3, done: false }
// 第二次调用 传递参数
g.next(2) // { value: 2, done: true }

错误处理机制

Generator函数内部可以部署错误处理代码,捕获函数体外抛出的错误。(不理解?)
不是应该捕获函数体内的错误吗? 异步操作发生在函数体内,为什么要处理函数体外的错误。

function* gen(x){
  try {
    var y = yield x + 2;
  } catch (e){
    console.log(e);
  }
  return y;
}

var g = gen(1);
g.next();
g.throw('出错了');
// 出错了

实例: 使用Generator解决异步读取文件

// 这里要借助Promise
//使用Promise 完成读取文件
const fs = require('fs')
// 实例化一个promise对象\
console.log('fs')
const readFilePromise = function(fileName){
	return new Promise((resolve,reject)=>{
  	fs.readFile(fileName,'utf-8',(err,data)=>{
    	if(err) reject(err)
        resolve(data)
    })
    
  })
}
// 声明 Generator函数
function* gen(){
    const res = yield readFilePromise('./index.html')
    // 请求成功之后 处理数据
    try{
    	res.then(data=>{
        console.log(data)
    	})
    }catch(err){
    	console.log(err)
    }
}