Generator

2020年2月25日

概念

Generator 函数是 ES6 提供的一种异步编程解决方案,

形式上,Generator 函数是一个普通函数,但是有两个特征

  • function关键字与函数名之间有一个星号*
  • 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)

值得注意的是Generator 函数被调用后,不会执行,也不会返回任何结果,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,这就状态就好像正在进行中,等待执行命令.

实例

function* makeBread() {
  yield '准备材料';
  yield '做面包';
  yield '放入烤箱';
  return '完成,开吃';
}

var to = makeBread()
to.next() //{value: "准备材料", done: false}
to.next()//{value: "做面包", done: false}
to.next()//{value: "放入烤箱", done: false}
to.next()//{value: "完成,开吃", done: true}
//已经全部完成,再执行就会输出
to.next()//{value: undefined, done: true}

上述例子中,一共调用了5次next方法

  1. Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值准备材料done属性的值false,表示遍历还没有结束。
  2. Generator 函数从上次yield表达式停下的地方,执行到下个yield表达式为止.开始"做面包"
  3. "放入烤箱"
  4. Generator 函数执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

也就是说,每一次调用 next方法,都会返回个有着valuedone两个属性的对象,直到done == true,表示是否遍历结束。

next 方法的参数

yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* makeBread() {
  var a, b, c

  a = yield '准备材料'
  
  if(a=="材料准备好了"){
    b = yield '做面包'
  }else{
    b = yield '没材料怎么做面包'
  }

  if(b=="面包做好了"){
    c = yield '放入烤箱'
  }else{
    c = yield '木得面包'
  }
  
  if(c=='放入烤箱')
  {
    return '完成,开吃'
  }else{
    return '吃空气咯'
  }
 
}

var to = makeBread()

to.next()   //{value: "准备材料", done: false}
to.next()   //{value: "没材料怎么做面包", done: false}
to.next()   //{value: "木得面包", done: false}
to.next()   //{value: "吃空气咯", done: true}

//传入参数:
to.next() //{value: "准备材料", done: false}
to.next("材料准备好了")//{value: "做面包", done: false}
to.next("面包做好了")//{value: "放入烤箱", done: false}
to.next("放入烤箱")//{value: "完成,开吃", done: true}

如上面例子所示,通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

for...of 循环

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}
var arr = []
for (let v of foo()) {
  arr.push(v)
}
arr // [1, 2, 3, 4, 5]
// 通过扩展运算符(...)、解构赋值和Array.from都能实现相同效果
[...foo()] // [1, 2, 3, 4, 5]
Array.from(foo())// [1, 2, 3, 4, 5]

上述例子中,6 并没有存取arr 数组之中,因为一旦返回对象的done属性为truefor...of循环就会中止,且不包含该返回对象.

throw()方法

throw() 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 donevalue 两个属性的对象。

function* gen() {
  while(true) {
    try {
       yield 42;
    } catch(e) {
      console.log("Error caught!");
    }
  }
}

var g = gen();
g.next(); // { value: 42, done: false }
g.throw(new Error("Something went wrong")); // "Error caught!"

Generator 函数的用法


function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}
var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
})

上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用 next 方法(第二行),执行异步任务的第一阶段。由于 Fetch 模块返回的是一个 Promise 对象,因此要用 then 方法调用下一个next 方法。

可以看到,虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

所以,ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 如何异步操作,将在后面的文章中描述.