ES6 Generators系列:

  1. ES6 Generators基本概念
  2. 深入研究ES6 Generators
  3. ES6 Generators的异步应用
  4. ES6 Generators并发

  如果你还不知道什么是ES6 generators,请看我的前一篇文章“ES6 Generators基本概念” 。如果你已经对它有所了解,本文将带你深入了解ES6 generators的一些细节。

错误处理

  ES6 generators设计中最牛逼的部分之一就是generator函数内部的代码是同步的,即使在generator函数外部控制是异步进行的。

  也就是说,你可以使用任何你所熟悉的错误处理机制来简单地在generator函数中处理错误,例如使用try..catch机制。

  来看一个例子:

function *foo() {
try {
var x = yield 3;
console.log( "x: " + x ); // 有可能永远也不会运行到这儿!
}
catch (err) {
console.log( "Error: " + err );
}
}

  尽管函数会在yield 3表达式的位置暂停任意长的时间,但是如果有错误被发回generator函数,try..catch依然会捕获该错误!你可以尝试在异步回调中调用上面的代码。

  那么,如何才能将错误精准地发回给generator函数呢?

var it = foo();

var res = it.next(); // { value:3, done:false }

// 这里我们不调用next(..)方法,而直接抛出一个异常:
it.throw( "Oops!" ); // Error: Oops!

  这里我们使用了另一个方法throw(..),它会在generator函数暂停的位置抛出一个错误,然后try..catch语句会捕获这个错误!

  注意:如果你通过throw(..)方法向generator函数抛出一个错误,但是该generator函数中并没有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(); // 可能会引发类型错误!
yield y;
} var it = foo(); it.next(); // { value:3, done:false } try {
it.next( 42 ); // 42没有toUpperCase()方法
}
catch (err) {
console.log( err ); // toUpperCase()引发TypeError错误
}

Generators委托

  你可以在一个generator函数体内调用另一个generator函数,不是通过普通的方式实例化一个generator函数,实际上是将当前generator函数的迭代控制委托给另一个generator函数。我们通过关键字yield *来实现。看下面的代码:

function *foo() {
yield 3;
yield 4;
} function *bar() {
yield 1;
yield 2;
yield *foo(); // yield *将当前函数的迭代控制委托给另一个generator函数foo()
yield 5;
} for (var v of bar()) {
console.log( v );
}
// 1 2 3 4 5

  注意这里我们依然推荐yield *foo()这种写法,而不用yield* foo(),我在前一篇文章中也提到过这一点(推荐使用function *foo(){}而不用function* foo(){})。事实上,在很多其它的文章和文档中也都采用了前者,这种写法会让你的代码看起来更清晰一些。

  我们来看一下上面代码的运行原理。在for..of循环遍历中,通过隐式调用next()方法将表达式yield 1yield 2的值返回,这一点我们在前一篇文章中已经分析过了。在关键字yield *的位置,程序实例化并将迭代控制委托给另一个generator函数foo()。一旦通过yield *将迭代控制从*bar()委托给*foo()(只是暂时性的),for..of循环将通过next()方法遍历foo(),因此表达式yield 3yield 4将对应的值返回给for..of循环。当对*foo()的遍历结束后,委托控制又重新回到之前的那个generator函数,所以表达式yield 5返回了对应的值。

  上面的代码很简单,只是通过yield表达式输出值。当然,你完全可以不通过for..of循环而手动通过next(..)方法并传入相应的值来进行遍历,这些传入的值也会通过yield *关键字传递给对应的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

  虽然这里我们只展示了一级委托,但理论上可以有任意多级委托,就是说上例中的generator函数*foo()中还可以有yield *表达式,从而将控制进一步委托给另外的generator函数,一级一级传递下去。

  还有一点就是yield *表达式允许接收被委托的generator函数的return返回值。

function *foo() {
yield 2;
yield 3;
return "foo"; // 字符串"foo"会被返回给yield *表达式
} 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()表达式,程序将控制委托给generator函数*foo(),当函数foo()执行完毕后,通过return语句将值(字符串"foo")返回给yield *表达式,然后在bar()函数中,这个值最终被赋值给变量v

  Yieldyield *之间有个很有趣的区别:在yield表达式中,接收的值是由随后的next(..)方法传入的参数,但是在yield *表达式中,它接收的是被委托的generator函数中return语句返回的值(此时通过next(..)方法将值传入的过程是透明的)。

  你也可以在yield *委托中进行双向错误处理:

function *foo() {
try {
yield 2;
}
catch (err) {
console.log( "foo caught: " + err );
} yield; // 暂停 // 抛出一个错误
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!" ); // 将会被foo()中的try..catch捕获
// foo caught: Uh oh! it.next(); // { value:undefined, done:true } --> 注意这里不会出现错误!
// bar caught: Oops!

  在上面的代码中,throw("Uh oh!")方法抛出一个错误,该错误被yield *委托的generator函数*foo()中的try..catch所捕获。同样地,* foo()中的throw "Oops!"语句将错误抛回给*bar(),然后被*bar()中的try..catch捕获。如果错误没有被捕获到,则会继续向上抛出。

总结

  从代码语义层面来看,generator函数是同步执行的,这意味着你可以在yield语句中使用try..catch来处理错误。另外,generator遍历器还有一个throw(..)方法,可以在其暂停的地方抛出一个错误,这个错误也可以被generator函数内部的try..catch捕获。

  关键字yield *允许你在当前的generator函数内部委托并遍历另一个generator函数。我们可以将参数通过yield *传入到被委托的generator函数体中,当然,错误信息也会通过yield *被传回来。

  到目前为止我们还有一个最基本的问题没有回答,那就是如何在异步模式中使用generator函数。前面我们看到的所有对generator函数的遍历都是同步执行的。

  关键是要构造一种机制,能够使generator函数在暂停的时候启动一个异步任务,然后在异步任务结束时恢复generator函数的执行(通过调用next()方法)。我们将在下一篇文章中探讨在generator函数中创建这种异步控制的各种方法。敬请关注!

深入研究ES6 Generators的更多相关文章

  1. ES6 Generators并发

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 如果你已经读过 ...

  2. ES6 Generators的异步应用

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 通过前面两篇文 ...

  3. ES6 Generators基本概念

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 在JavaSc ...

  4. ES6 generators in depth 一(译)

    今天在学习redux-saga时,外部链接推荐了这篇文章ES6 generators in depth,所以翻译的同时也可以加深一下对Generator的理解. 这里对原文一些只能在高版本现代浏览器使 ...

  5. [ES6] Generators

    Example 1: function *topicList(){ yield "ES2015"; yield "Semi-colons: good or bad?&qu ...

  6. ES6深度解析3:Generators

    介绍ES6 Generators 什么是Generators(生成器函数)?让我们先来看看一个例子. function* quips(name) { yield "hello " ...

  7. 【转向Javascript系列】深入理解Generators

    随着Javascript语言的发展,ES6规范为我们带来了许多新的内容,其中生成器Generators是一项重要的特性.利用这一特性,我们可以简化迭代器的创建,更加令人兴奋的,是Generators允 ...

  8. 【翻译】ES6生成器简介

    原文地址:http://davidwalsh.name/es6-generators ES6生成器全部文章: The Basics Of ES6 Generators Diving Deeper Wi ...

  9. 每个JavaScript开发人员应该知道的33个概念

    每个JavaScript开发人员应该知道的33个概念 介绍 创建此存储库的目的是帮助开发人员在JavaScript中掌握他们的概念.这不是一项要求,而是未来研究的指南.它基于Stephen Curti ...

随机推荐

  1. tomcat线程初探

    博主:handsomecui,希望路过的各位大佬留下你们宝贵的意见,在这里祝大家冬至快乐. 缘由: 初探缘由,在业务层想要通过(当前线程的栈)来获取到控制层的类名,然后打日志,可是发现并不能通过当前线 ...

  2. 【分享】iTOP4412开发板-Bluetooth移植文档

    [分享]iTOP4412开发板-Bluetooth移植文档 最近须要把Bluetooth移植到iTOP-4412 开发平台.查阅了相关资料,经过一段时间的研究.调试,最终成功的将蓝牙功能移植到了开发板 ...

  3. OR1200中指令Cache的结构

    下面内容摘自<步步惊芯--软核处理器内部设计分析>一书 12.3 ICache结构 OR1200中实现ICache的文件有or1200_ic_top.v.or1200_ic_fsm.v.o ...

  4. M03 利用Accord 进行机器学习的第一个小例子

    01 安装 Visual studio 2017. 不具备安装这个的话,也可安装,Microsoft Visual Studio Express (or equivalent) 02 创建 C# 的 ...

  5. 大数据学习(2)HDFS文件管理

    命令行管理HDFS [root@server1 bin]# hadoop fs Usage: hadoop fs [generic options] [-appendToFile <locals ...

  6. gunicorn 信号处理(SIGHUP,SIGUSR2)

    在这篇文章中,提到了Master进程对信号的处理函数,其中有两个信号比较有意思. SIGHUP:用来热更新(Reload)应用 SIGUSR2:用来在线升级(upgrade on the fly)gu ...

  7. 修改文件系统属性chattr,查看文件系统属性lsattr

    chattr chattr +i 文件或目录 , chattr +a 文件或目录,chattr -i 文件或目录,chattr -a 文件或目录,chattr =i 文件或目录,chattr =a 文 ...

  8. 虚拟表dual。字符串函数UPPER,LOWER。&变量。INITCAP,LENGTH,SUBSTR

    &自定义变量的用法:

  9. Solr集群搭建详细教程(二)

    注:欢迎大家转载,非商业用途请在醒目位置注明本文链接和作者名dijia478,商业用途请联系本人dijia478@163.com. 之前步骤:Solr集群搭建详细教程(一) 三.solr集群搭建 注意 ...

  10. 【Zookeeper】源码分析目录

    Zookeeper源码分析目录如下 1. [Zookeeper]源码分析之序列化 2. [Zookeeper]源码分析之持久化(一)之FileTxnLog 3. [Zookeeper]源码分析之持久化 ...