Generator和yield 分析
项目中使用了koa,最近在学习koa的源码,先把这些东西都写下来,免得以后忘记了。
-
koa源码学习前先注意下面这三个概念
generator function (生成器函数)
generator (生成器)
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点:
通过new运算符或函数调用的形式调用生成器函数,均会返回一个生成器实例;
通过new运算符或函数调用的形式调用生成器函数,均不会马上执行函数体的代码;
必须调用生成器实例的next方法才会执行生成器函数体的代码。
关键字yield
用于马上退出代码块并保留现场,当执行迭代器的next函数时,则能从退出点恢复现场并继续执行下去。
一旦在 yield expression 处暂停, 除非外部调用生成器的 next() 方法,否则生成器的代码将不能继续执行.
这使得可以对生成器的执行以及渐进式的返回值进行直接控制.
下面有2点需要注意:
yield后面的表达式将作为迭代器next函数的返回值;
迭代器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 insidefoo()
// 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*