generator是ES6提供的一种异步编程解决方案,在语法上,能够把它理解为一个状态机,内部封装了多种状态。执行generator,会生成返回一个遍历器对象。返回的遍历器对象,能够依次遍历generator函数的每个状态。同时ES6规定这个遍历器是Generator函数的实例,也继承了Genarator函数的prototype对象上的方法。编程
最简单的generator函数,其实它就是一个普通的函数,可是它有两个特征。第一就是function关键字与函数名之间有一个*号,其二就是函数体内使用yield表达式来遍历状态:promise
function* newGenerator(){ yield 'hello'; yield 'world'; return 'ending'; }
执行generator函数以后,该函数并不会当即执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。一般使用遍历器对象的next方法。使得指针移向下一个状态。每一次调用next()方法,内部指针就从函数头部或上一次停下里的地方开始执行,直到遇到下一个yield表达式位置,由此能够看出,generator是分段执行的,yield表达式是暂停执行的标记,而next方法能够恢复执行。数据结构
yield表达式在generator中是做为一个暂停标志,当碰到yield时,函数暂停执行,等到下一次next()执行时,函数才从当前yield位置开始执行。而且,yield表达式只能用在Generator函数里边;同时,yield若是后边带一个,则就是至关于一个for...of的简写形式,若是yield后边不带,则返回的是generator的值。多线程
function* gen() { yield 'hello'; yield* 'hello'; } let f = gen(); console.log(f.next().value); console.log(f.next().value); console.log(f.next().value); console.log(f.next().value); console.log(f.next().value);
上述例子中的后四个next()函数,就会顺序的返回h e l l异步
经过next函数,能够执行对应的yield表达式,且next()函数还能够带参数,该参数能够做为上一次yield表达式的返回值,由于yield自己是没有返回值的,若是next()中不带参数,则yield每次运行以后的返回值都是为undefined;async
function* dataConsumer() { console.log('Started'); console.log(`1. ${yield}`); console.log(`2. ${yield}`); return 'result'; } let genObj = dataConsumer(); genObj.next(); // Started genObj.next('a'); // 1. a genObj.next('b'); // 2. b
上述函数中,第一次运行next(),运行到第一个next()函数截止,第二个next运行时,传入的参数为'a';则运行到第二个yield地方截止,而后第一个yield运行的返回值为'a',依次类推,则获得上述结果。编辑器
另外,经过for...of能够循环generator中的全部状态,而且不须要使用next()函数。除了for...of循环之外,扩展运算符(...),解构赋值和Array.form方法内部调用的,都是遍历器接口。异步编程
generator生成的对象,还有其余一些函数,好比throw()用来抛出错误,return()用来定义返回值并终止generator的状态。函数
以上的三个方法在本质上实际上是同样的,他们就是让generator恢复执行,而且使用不一样的语句来替代yield语句。lua
协程能够理解为“协做的线程”或者“协做的函数”。协程既能够是单线程实现,也能够用多线程实现,前者是一种特殊的子例程,后者是一种特殊的线程。
协程有点像函数,又有点像线程,它的运行流程大体以下。
协程适合用于多任务运行环境,它与普通的线程很类似,都有本身的执行上下文,能够分享全局量。他们的不一样之处在于,同一时间能够有多个线程处于运行状态,可是运行的协程只能有一个,其余协程都是处于暂停状态。
因为JavaScript是单线程,只能保持一个调用栈,引入协程以后,每个任务能够保持本身的调用栈,这样就能够再抛出错误的时候找到原始的调用栈,不至于像异步操做的回调函数那样,一旦出错,原始的调用栈早就结束了。
Generator 函数是 ES6 对协程的实现,但属于不彻底实现。Generator函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator函数。若是是彻底执行的协程,任何函数均可以让暂停的协程继续执行。
若是将Generator函数当作协程,彻底能够将多个须要互相协做的任务写成Generator函数,他们之间使用yield标识交换控制权。
Generator 函数执行产生的上下文环境,一旦遇到yield命令,就会暂时退出堆栈,可是并不消失,里面的全部变量和对象会冻结在当前状态。等到对它执行next命令时,这个上下文环境又会从新加入调用栈,冻结的变量和对象恢复执行。
上述的介绍中,咱们看到了generator是什么,下边咱们看一下,目前中,咱们使用最多的,generator函数的异步调用。
异步编程对于单线程的JavaScript无疑是十分重要的(能够笼统的将异步定义为不连续的执行)。以前的文章中,咱们也说过,JavaScript中对于异步的实现,就是回调函数。咱们以前也有使用过promise去进行死亡回调的改良,promise来使回调嵌套的表现形式更好了些。其实呢generator函数也能够用来实现异步回调的嵌套。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操做须要暂停的地方,都用yield语句注明。除此以外,它还有两个特性,使它能够做为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。即为next方法还能够接收参数,向Generator函数体内输入数据。
Thunk函数是自动执行Generator函数的一种方法。
对于之前的函数参数的求值计算,有两种计算方式,一种是传值调用,一种是传名调用。编辑器中的“传名调用”的实现,每每是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫作Thunk函数。
JavaScript中使用的是传值调用,它的Thunk函数含义有所不一样。再JavaScript中,替换的不是表达式,而是多参数函数的休整。
Thunk函数能够用于Generator函数的自动流程管理。即便Generator函数能够自动执行。
co模块用于Generator函数的自动执行。co函数返回一个Promise对象,所以能够用then方法添加回调函数。
使用了Generator函数以后,咱们能够将多个异步嵌套的代码改成同步写异步,大大的简化了咱们的代码量,以及代码的美观。
上边就是大概的ES中的Generator函数的介绍,咱们可使用Generator来实现 lazy evaluation,Iterator和实现异步调用同步的写法,可是其中咱们面临的更多的仍是generator的执行问题,下一篇咱们来看一下generator是怎么实现async/await来控制异步。以及co模块的实现。