什么是Generators(生成器函数)?让咱们先来看看一个例子。javascript
function* quips(name) { yield "hello " + name + "!"; yield "i hope you are enjoying the blog posts"; if (name.startsWith("X")) { yield "it's cool how your name starts with X, " + name; } yield "see you later!"; }
这是一只会说话的猫的一些代码,多是当今互联网上最重要的一种应用。它看起来有点像一个函数,对吗?这被称为生成器-函数,它与函数有不少共同之处。但你立刻就能看到两个不一样之处。html
当你调用生成器-函数quips()时会发生什么?html5
> var iter = quips("jorendorff"); [object Generator] > iter.next() { value: "hello jorendorff!", done: false } > iter.next() { value: "i hope you are enjoying the blog posts", done: false } > iter.next() { value: "see you later!", done: false } > iter.next() { value: undefined, done: true }
你可能已经很是习惯于普通函数和它们的行为方式。当你调用它们时,它们会当即开始运行,并一直运行到返回或抛出异常。全部这些对任何JS程序员来讲都是次日性。调用一个生成器看起来也是同样的:quips("jorendorff")。java
可是当你调用一个生成器时,它尚未开始运行。相反,它返回一个暂停的Generator对象iter(在上面的例子中调用)。你能够把这个Generator对象看做是一个函数调用,在调用前被冻结。具体来讲,它被冻结在生成器函数的顶端,就在运行其第一行代码以前。每次你调用Generator对象的方法.next()时,函数调用都会自我解冻,并运行到下一个yield表达式为止。这就是为何咱们每次调用上面的iter.next()方法,都会获得一个不一样的字符串值。这些都是由函数quips()中产生的值。在最后一次iter.next()调用中,咱们终于到达了生成器-函数的终点,因此结果的字段是 。到达一个函数的终点就像返回同样,这就是为何结果的字段是 { value: undefined, done: true }git
如今多是一个好时机,回到会说话的猫的演示页面,真正地玩一玩代码。试着把yield放在一个循环里面。会发生什么?从技术上讲,每次Generator执行yield时,它的堆栈--局部变量、参数、临时值以及当前在Generator主体中的执行位置--都会从堆栈中删除。然而,Generator对象会保留对这个堆栈框架的引用(或副本),以便之后.next()调用能够从新激活它并继续执行。程序员
值得指出的是,Generator不是线程。在有线程的语言中,多段代码能够同时运行,一般会致使竞赛条件、非肯定性和甜蜜的性能。Generator则彻底不是这样的。当一个Generator运行时,它与调用者在同一个线程中运行。执行的顺序是顺序的、肯定的,而不是并发的。与系统线程不一样,Generator只在其函数体中标明的yield点上暂停运行。github
好了。咱们知道Generator是什么。咱们已经看到了一个Generator的运行,暂停本身,而后恢复执行。如今有个大问题。这种奇怪的能力怎么可能有用?编程
ES6迭代器不只仅是一个单一的内置类。它们是该语言的一个扩展点。你能够经过实现两个方法[Symbol.iterator]()和next()来建立你本身的迭代器。可是实现一个接口至少要作一点工做。让咱们看看迭代器的实如今实践中是什么样的。做为一个例子,让咱们作一个简单的迭代器range for (;;),它只是从一个数字到另外一个数字进行计数,就像一个老式的C循环同样。数组
// This should "ding" three times for (var value of range(0, 3)) { alert("Ding! at floor #" + value); }
这里有一个解决方案,使用ES6类class。并发
class RangeIterator { constructor(start, stop) { this.value = start; this.stop = stop; } [Symbol.iterator]() { return this; } next() { var value = this.value; if (value < this.stop) { this.value++; return {done: false, value: value}; } else { return {done: true, value: undefined}; } } } // Return a new iterator that counts up from 'start' to 'stop'. function range(start, stop) { return new RangeIterator(start, stop); }
这就是在Java或Swift中实现迭代器的状况。这并不坏。但也不彻底是微不足道的。这段代码里有什么错误吗?这可很差说。它看起来彻底不像咱们在这里试图模仿的原始循环:for (;;),迭代器协议迫使咱们拆除了循环。在这一点上,你可能对迭代器感到有点冷淡。它们可能很好用,但彷佛很难实现。
你可能不会想建议咱们在JS语言中引入一个疯狂的、使人费解的新控制流结构,只是为了使迭代器更容易构建。但既然咱们有生成器Generator,咱们能在这里使用它们吗?让咱们试试吧。
function* range(start, stop) { for (var i = start; i < stop; i++) yield i; }
上面的4行range()代码能够直接替代之前的23行实现,包括整个类RangeIterator。就是由于Generator是迭代器,因此这一切才是可能的。全部的生成器都有一个内置的next()和[Symbol.iterator]()的实现。 你只需写出循环的行为。
在没有Generator的状况下实现迭代器,就像被迫彻底用被动语态来写一封长邮件。当简单地说出你的意思不是一个选项时,你最终说的东西可能会变得至关复杂。"个人意思是,个人意思是,我必须在不使用循环语法的状况下描述一个循环的功能,因此RangeIterator又长又奇怪。而Generator就是答案。
咱们还能够如何利用生成器做为迭代器的能力呢?
简化建数组函数。假设你有一个函数,每次调用都会返回一个数组的结果,就像下面这个函数:
//将一维数组'图标'切分红长度为'rowLength'的数组 function splitIntoRows(icons, rowLength) { var rows = []; for (var i = 0; i < icons.length; i += rowLength) { rows.push(icons.slice(i, i + rowLength)); } return rows; }
使用Generator会让这种代码更短一些。
function* splitIntoRows(icons, rowLength) { for (var i = 0; i < icons.length; i += rowLength) { yield icons.slice(i, i + rowLength); } }
执行时惟一区别是,它不是一次性计算全部的结果并返回一个数组,而是返回一个迭代器,而后根据须要逐个计算结果。
处理可迭代数据的工具。ES6并无提供一个扩展库,用于过滤、映射,以及通常状况下对任意的可迭代数据集进行任意的处理。可是Generator对于构建你所须要的工具来讲是很是棒的,只须要几行代码。例如,假设你须要一个新的在DOM NodeLists上遍历的方法,而不只仅是Arrays。小菜一碟:建立Array.prototype.filter
function* filter(test, iterable) { for (var item of iterable) { if (test(item)) yield item; } }
那么Generator是否有用呢?固然,它们是实现自定义迭代器的一种惊人的简单方法,并且迭代器是整个ES6的数据和循环的新标准。但这并非Generator的所有功能。这甚至可能不是它们所作的最重要的事情。
下面是我前段时间写的一些JS代码。
}; }) }); }); }); });
也许你已经在本身的代码中看到了这样的东西。异步API一般须要一个回调,这意味着每次你作什么都要写一个额外的匿名函数。所以,若是你有一点代码作三件事,而不是三行代码,你就会看到三个缩进层次的代码。下面是我写的一些更多的JS代码。
}).on('close', function () { done(undefined, undefined); }).on('error', function (error) { done(error); });
异步API有错误处理惯例,而不是异常。不一样的API有不一样的约定。在大多数API中,默认状况下,错误会被默默地放弃。在一些API中,即便是普通的成功完成也是默认放弃的。直到如今,这些问题都是咱们为异步编程付出的代价。咱们已经接受了这样的事实:异步代码看起来并不像相应的同步代码那样漂亮和简单。
Generator提供了新的但愿:咱们没必要再写那样丑陋的代码。
Q.async()是一个实验性的尝试,它使用Generators与Promises来产生相似于相应同步代码的异步代码。好比说:
// Synchronous code to make some noise. function makeNoise() { shake(); rattle(); roll(); } // Asynchronous code to make some noise. // Returns a Promise object that becomes resolved // when we're done making noise. function makeNoise_async() { return Q.async(function* () { yield shake_async(); yield rattle_async(); yield roll_async(); }); }
主要的区别是,异步版本必须在调用异步函数的每一个地方添加关键字yield。在该版本中添加像语句if或try/catch块这样的代码,就像在普通的同步版本中添加它同样。与其余编写异步代码的方式相比,这感受不像是在学习一种全新的语言。(延伸阅读)
所以,Generator为一种新的异步编程模型指明了方向,它彷佛更适合人类的大脑。这项工做正在进行中。在其余方面,更好的语法可能会有帮助。一项关于异步函数的建议,创建在Promises和Generators的基础上,并从C#的相似功能中得到灵感,已被提上ES7的议程。