Generator和yield 分析

  • 项目中使用了koa,最近在学习koa的源码,先把这些东西都写下来,免得以后忘记了。

  • koa源码学习前先注意下面这三个概念

    1. generator function (生成器函数)

    2. generator (生成器)

    3. yield

下面是一坨很简单的代码

function *gen() {

    yield 'sd';
}
var g = gen();

上面的代码中 gen是一个generator function, g是一个generator, yield 只是一个语法糖,下面会具体介绍


iterator和generator特性借鉴于Python, Ruby, smalltalk, 
本来用于方便访问容器内各个元素. 该特性需要node v0.11.9并开启–harmony特性才能使用, 
在chrome(29+)浏览器中需要在chrome://flags/ 开启Enable Experimental JavaScript选项, 然后重启.
   

generator

下面先介绍一下迭代器的概念,英文版本的点击这里

一个迭代器(对象)会有一个名为 next 的方法, 
调用该方法后会返回一个拥有两个属性的对象, 一个是 value 属性, 值可以是任意值, 以及一个 done 属性, 布尔值, 
表示该迭代器是否已经被迭代完毕,
类似{done: true/false, value: returnValue}结构数据    

小贴士:

String, Array, TypedArray, Map and Set 都是内建的迭代器,
一些表达式希望后面是iterable的,比如for of , yield*, 和析构复制

[..."abcd"] //看下这个语句返回什么呢?

此外,generator 还有一个throw 方法,可以进行异常处理

generator 就是一个迭代器, 含有next方法
每当调用 next() 的时候,generator function内部就会执行直到遇到下一个 yield 语句,然后暂停在那里,并返回一个对象。

generator function

普通函数添加*号后则成为了成为了生成器函数了。

// 定义生成器函数

function *enumerable(msg){
      console.log(msg)
      var msg1 = yield msg + '  after '
      console.log(msg1)
      var msg2 = yield msg1 + ' after'
      try{
        var msg3 = yield msg2 + 'after'
        console.log('ok')
      }catch(e){
        console.log(e)
      }
      console.log(msg2 + ' over')
}

// 初始化迭代器
var enumerator = enumerable('hello')
var ret = enumerator.next() // 控制台显示 hello,ret的值{value:'hello after',done:false}
ret =  enumerator.next('world') // 控制台显示 world,ret的值{value:'world after',done:false}
ret = enumerator.next('game') // 控制台显示game,ret的值{value:'game after',done:false}
// 抛出异常信息
ret = enumerator.throw(new Error('test')) // 
//控制台显示new Error('test')信息,然后显示game over。ret的值为{done:true}

生成器函数的行为与普通函数并不相同,表现为如下3点:

  1. 通过new运算符或函数调用的形式调用生成器函数,均会返回一个生成器实例;

  2. 通过new运算符或函数调用的形式调用生成器函数,均不会马上执行函数体的代码;

  3. 必须调用生成器实例的next方法才会执行生成器函数体的代码。

关键字yield

用于马上退出代码块并保留现场,当执行迭代器的next函数时,则能从退出点恢复现场并继续执行下去。

一旦在 yield expression 处暂停,  除非外部调用生成器的 next() 方法,否则生成器的代码将不能继续执行.

这使得可以对生成器的执行以及渐进式的返回值进行直接控制.

下面有2点需要注意:

  1. yield后面的表达式将作为迭代器next函数的返回值;

  2. 迭代器next函数的入参将作为yield的返回值(有点像运算符)。

针对上面的例子,
var ret = enumerator.next()// {value:'hello after',done:false}
enumerator.next('msg1 result');//这时候msg1的值是 msg1 result;

yield* 和 yield的区别

yield* 一个可迭代对象,就相当于把这个可迭代对象的所有迭代值分次 yield 出去。
yield* 表达式本身的值就是当前可迭代对象迭代完毕时的那个返回值(也就是迭代器的迭代值的 done 属性为 true 时 value 属性的值)。

function* g1() {

   yield 2;
   yield 3;
   yield 4;

}

function* g2() {

   yield 1;
   yield* g1();
   yield 5;

}

var iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true}

//返回值例子

function* g4() {

   yield* [1, 2, 3];
   return "foo";

}
var result;
function* g5() {

   result = yield* g4();

}
var iterator = g5();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true},
此时 g4() 返回了 { value: "foo", done: true }
console.log(result); // "foo"

具体可以参考yield *

异常处理

可以通过throw 抛出异常,在外层try catch, 具体可以参考这里

function *foo() {

try {
    yield 2;
}
catch (err) {
    console.log( "foo caught: " + err );
}

yield; // pause

// now, throw another error
throw "Oops!";

}

function *bar() {

yield 1;
try {
    yield *foo();
}
catch (err) {
    console.log( "bar caught: " + err );
}

}
var it = bar();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.throw( "Uh oh!" ); // will be caught inside foo()
// foo caught: Uh oh!
it.next(); // { value:undefined, done:true } --> No error here!
// bar caught: Oops!

其他

generator 需要不停地调用next方法,但是在项目中我们也没有手动的调用next方法,这是为什么呢?........

很牛的co模块就要登场了,请翻看下一篇

参考文章

https://developer.mozilla.org/zh/docs/Web/JavaScript/Guide/Iterators_and_Generators
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*


qinghui
164 声望8 粉丝

码农一枚,目前在美团做前端工作