深入探讨ES6生成器
如果对于ES6生成器不熟悉,请先阅读并运行下http://www.cnblogs.com/linda586586/p/4282359.html里面的代码。当你感觉掌握了基础之后,我们可以深入探讨一些细节。
错误处理
在ES6生成器设计中最强大的是一个生成器内部代码的语义是同步的,即使外部迭代控制是异步的。
可以使用简单的也许你很熟悉的错误处理技术--就是try-catch机制。
例如:
function *foo() {
try {
var x = yield 3;
console.log( "x: " + x ); // may never get here!
}
catch (err) {
console.log( "Error: " + err );
}
}
即使函数会在yield3处暂停,也许会保持暂停状态任意一段时间,如果回传给生成器错误,try-catch会捕获它!试着使用普通异步方法处理,像回调函数。
但是,错误回传给这个生成器有多精确?
var it = foo();
var res = it.next(); // { value:3, done:false }
// instead of resuming normally with another `next(..)` call,
// let's throw a wrench (an error) into the gears:
it.throw( "Oops!" ); // Error: Oops!
可以看到,在迭代器中用到的另外一个方法--throw(),会向生成器抛错,就好像正好发生在生成器yield暂停位置的点上。try-catch语句就像所期望的那样捕获了错误!
注:如果向生成器内抛错,但是没有try-catch捕获它,错误会传播回去。所以:
function *foo() { }
var it = foo();
try {
it.throw( "Oops!" );
}
catch (err) {
console.log( "Error: " + err ); // Error: Oops!
}
显然,反方向的错误处理也起作用了:
function *foo() {
var x = yield 3;
var y = x.toUpperCase(); // could be a TypeError error!
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next( 42 ); // `42` won't have `toUpperCase()`
}
catch (err) {
console.log( err ); // TypeError (from `toUpperCase()` call)
}
代理生成器
另外想要做的是从生成器函数内部调用另外一个生成器。这意思不仅仅是用普通的方法实例化生成器,实际上是对于其他生成器代理迭代控制。为了这样做,可以使用yield关键字的一个变形:yield *("yield star").例子:
function *foo() {
yield 3;
yield 4;
}
function *bar() {
yield 1;
yield 2;
yield *foo(); // `yield *` delegates iteration control to `foo()`
yield 5;
}
for (var v of bar()) {
console.log( v );
}
// 1 2 3 4 5
就像前面一篇介绍的,这里也使用yield *foo()代替了其他文章里面的yield* foo()。我认为它可以更确切的说明在发生的事情。
让我们看看这个是怎么工作的。yield 1和yield 2直接把他们的值送出到了for of循环的next()调用,像我们所了解和期望的那样。
但是yield*被碰到了,你会注意到我们正通过实例化foo()进入另外一个生成器.所以我们在为另外一个生成器迭代进行代理。
当yield*从*bar()到*foo()代理时,for-of循环的next()调用是控制foo(),然而yield 3和yield4将他们的值送出去到for-of循环。
当*foo()结束的时候,控制返回到原始的生成器中,最后调用yield 5。
简化下,这个例子只向外yields值。但是当然,如果不用for-of循环,只是手动调用迭代器的next(),并且传递信息到里面,那些信息会以同样期望的方式通过yield*代理传递。
function *foo() {
var z = yield 3;
var w = yield 4;
console.log( "z: " + z + ", w: " + w );
}
function *bar() {
var x = yield 1;
var y = yield 2;
yield *foo(); // `yield*` delegates iteration control to `foo()`
var v = yield 5;
console.log( "x: " + x + ", y: " + y + ", v: " + v );
}
var it = bar();
it.next(); // { value:1, done:false }
it.next( "X" ); // { value:2, done:false }
it.next( "Y" ); // { value:3, done:false }
it.next( "Z" ); // { value:4, done:false }
it.next( "W" ); // { value:5, done:false }
// z: Z, w: W
it.next( "V" ); // { value:undefined, done:true }
// x: X, y: Y, v: V
即使我们只在这里演示了一层代理,*foo()没有理由不能为另外的生成器迭代器yield代理,然后再一个,以此类推。
另外一个yield可以玩的把戏是从代理生成器接收return值。
function *foo() {
yield 2;
yield 3;
return "foo"; // return value back to `yield*` expression
}
function *bar() {
yield 1;
var v = yield *foo();
console.log( "v: " + v );
yield 4;
}
var it = bar();
it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // "v: foo" { value:4, done:false }
it.next(); // { value:undefined, done:true }
可以看到,yield *foo()代理了迭代控制(next()调用)直到他结束,然后来自foo()的任何返回值被置为yield*表达式的结果,然后赋值给了本地变量v.
yield和yield *有一个有意思的区别:用yield表达式,结果是被随后的next()送进来的,但是用yield*,它只从它的代理的return值接受结果。
也可以从两个方向上进行错误处理,通过yield*代理:
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!
可以看到,throw("Uh oh!")在*foo()内通过yield*代理抛错到了try-catch。在*foo()内的throw "Oops!"抛出来回到了*bar(),然后通过另外一个try-catch捕获了。要是没有捕获到他们其中的一个,错误会像期望的那样继续向外传播。
总结
生成器有同步处理机制,即可以通过yield声明使用try-catch错误处理。生成器迭代器也有一个throw()方法在生成器暂停的位置抛错,当然也可以被生成器内部的try-catch捕获。
yield允许从当前的生成器为另外一个代理迭代控制。结果是yield*在两个方向上传递,可以是信息也可以是错误。
但是,还有一个基本问题遗留了,没有回答:生成器怎么通过同步代码模式帮助我们?所有我们现在看到的这两篇文章是生成器函数的同步迭代器。
关键是构建一个生成器暂停来启动异步任务的机制,然后在异步任务最后恢复。我们将探讨很多通过生成器生成这样异步控制的方法。
英文原文:http://davidwalsh.name/es6-generators-dive
深入探讨ES6生成器的更多相关文章
- 学习ES6生成器(Generator)
背景 在JS的使用场景中,异步操作的处理是一个不可回避的问题,如果不做任何抽象.组织,只是“跟着感觉走”,那么面对“按顺序发起3个ajax请求”的需求,很容易就能写出如下代码(假设已引入jQuery) ...
- ES6生成器基础
ES6引进的最令人兴奋的特性就是一种新的函数生成方式,称为生成器(generator).名称有点奇怪,但是第一眼看上去行为更加奇怪.文章主要介绍生成器如何工作,然后让你明白为什么他们对于未来的JS会有 ...
- 【翻译】ES6生成器简介
原文地址:http://davidwalsh.name/es6-generators ES6生成器全部文章: The Basics Of ES6 Generators Diving Deeper Wi ...
- ES6生成器函数generator
ES6生成器函数generator generator是ES6新增的一个特殊函数,通过 function* 声明,函数体内通过 yield 来指明函数的暂停点,该函数返回一个迭代器,并且函数执行到 y ...
- ES6生成器与迭代器
ES6迭代器的一个例子 function run(taskDef) { var task = taskDef(); var result = task.next(); // 递归执行迭代 functi ...
- 探讨ES6的import export default 和CommonJS的require module.exports
今天来扒一扒在node和ES6中的module,主要是为了区分node和ES6中的不同意义,避免概念上的混淆,同时也分享一下,自己在这个坑里获得的心得. 在ES6之前 模块的概念是在ES6发布之前就出 ...
- 轻松学会ES6新特性之生成器
生成器虽然是ES6最具魔性的新特性,但也是最难懂得的一节,笔者写了大量的实例来具体化这种抽象的概念,能够让人一看就懂,目的是希望别人不要重复或者减少笔者学习生成器的痛苦经历. 在说具体的ES6生成器之 ...
- ES6最具魔力的特性——生成器
ES6生成器(Generators)简介 我们从一个示例开始: function* quips(name) { yield "你好 " + name + "!" ...
- 深入浅出ES6(三):生成器 Generators
作者 Jason Orendorff github主页 https://github.com/jorendorff ES6生成器(Generators)简介 什么是生成器? 我们从一个示例开始: ...
随机推荐
- redis使用日志(一) 安装,调试
第一步:下载,安装 root@iZ28fgejjw7Z:/# wget http://download.redis.io/releases/redis-.tar.gz root@iZ28fgejjw7 ...
- 斑马打印机网卡ZebraNet配置(有线)
实图: 抽象图: 说明: 1.并口,用于连接斑马打印机一端 2.网络连接状态指示灯 3.打印状态指示灯 4.测试按钮,在连接打印机的情况下,按下此键,会打印出网卡信息. 5.网线接口 按下测试按钮之后 ...
- redis info命令详解
以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和统计数值. 通过给定可选的参数 section ,可以让命令只返回某一部分的信息: server : 一般 Red ...
- android之ExpandableListActivity
MainActivity.java package com.example.mars_2300_expandablelist; import java.util.ArrayList; import j ...
- ruby字符串学习笔记5
1获取字符串某部分 s = "My kingdom for a string!" s.slice(3,7) # kingdom s[3,7] # kingdom s[/.ing/] ...
- C++学习31 重载=(赋值运算符)
和普通变量一样,对象之间也可以相互赋值.赋值运算符“=”可以用来将一个对象拷贝给另一个已经存在的对象.对象之间的赋值是将成员变量依次拷贝,而不是将整个对象的内存按位拷贝. 对象之间的赋值: #incl ...
- C++学习28 重载>>和<<(输入输出运算符)
在C++中,系统已经对左移运算符“<<”和右移运算符“>>”分别进行了重载,使其能够用于输入输出,但是输入输出的处理对象只能是系统内建的数据类型.系统重载这两个运算符是以系统类 ...
- 一些BOOTSTRAP的问题
老师好,有几个点不是很明白,劳烦老师帮忙解惑 <1>不是特别清楚nav和navbar的区别,视频中用的是nav标签,而class则标明navbar,是不是这里的nav其实没有什么作用,而c ...
- delphi SPCOMM 接收数据不完整!该如何解决
SPCOMM 接收数据不完整!该如何解决 SPCOMM 接收数据不完整!我作了一个 读取地磅数据的程序,是用spcomm接收的! 总共有五台地磅,其他4台地磅数据读取都正常.但是有一台接收数据的时 ...
- Lua 架构 The Lua Architecture
转载自:http://magicpanda.net/2010/10/lua%E6%9E%B6%E6%9E%84%E6%96%87%E6%A1%A3/ Lua架构文档(翻译) 十 102010 前段时间 ...