Generators算得上js的一个新概念函数。它看起来像是一个函数,但是可以暂停执行。从语法上看,有3点关注:
在node 0.11以上,对node必须加入--harmony 参数,即可支持:
$ node --harmony > function *a(){} undefined >
看到undefined就说明支持了。如果不加参数,默认不支持。你会看到
$ node > function *a(){} ...
声明一个generator 是这样的:
function* ticketGenerator() {}
如果想要 generator 提供一个值并暂停,那么需要使用yeild 关键字。yield 就像 return 一样返回一个值。和它不同的是,yield会暂停函数。
function* ticketGenerator() { yield 1; yield 2; yield 3; }
我们做了一个迭代器,叫做 ticketGenerator. 我们可以和它要一个值,然后它返回1,然后暂停。依次返回2,3:
var takeANumber = ticketGenerator(); takeANumber.next(); // > { value: 1, done: false } takeANumber.next(); // > { value: 2, done: false } takeANumber.next(); // > { value: 3, done: false } takeANumber.next(); // > { value: undefined, done: true }
现在我们返回最大为3 ,不太有用.我们可以递增到无穷。无穷数列来了。
function* ticketGenerator() { for(var i=0; true; i++) { yield i; } }
再来一遍:
var takeANumber = ticketGenerator(); console.log(takeANumber.next().value); //0 console.log(takeANumber.next().value); //1 console.log(takeANumber.next().value); //2 console.log(takeANumber.next().value); //3 console.log(takeANumber.next().value); //4
每次叠加,无穷枚举。这就有点意思了。
除了可以枚举累加外, next() 还有第二种用法:如果你传递一个值给next,它会作为yield 语句的返回值。我们可以利用这个特性,把刚刚的无限数列做一次重置:
function* ticketGenerator() { for(var i=0; true; i++) { var reset = yield i; if(reset) { i = -1; } } }
这样当我们调用next(true)的时候,i会等于-1,从而重设了i值到初始值。
无需困惑, yield i 会把i发到generator的调用next处,但是generator内部我们使用next提供的值(如果提供了的话)。
看看效果:
var takeANumber = ticketGenerator(); console.log(takeANumber.next().value); //0 console.log(takeANumber.next().value); //1 console.log(takeANumber.next().value); //2 console.log(takeANumber.next(true).value); //0 console.log(takeANumber.next().value); //1
这就是generator。古怪,有趣。接下来会继续分析它的应用,在解决callback hell方面。
拿一个案例开刀吧。我们来看一个delay函数,延迟一些时间,然后打印点文字:
function delay(time, callback) { setTimeout(function () { callback("Slept for "+time); }, time); }
调用它也是常见代码:
delay(1000, function(msg) { console.log(msg); delay(1200, function (msg) { console.log(msg); } }) //...waits 1000ms // > "Slept for 1000" //...waits another 1200ms // > "Slept for 1200"
为了保证两次打印的次序,我们唯一的办法,就是通过callback来完成。
要是延迟多几次,比如12次,我们需要12次嵌套的callback,代码不段向右延伸。哇,回调金字塔。
异步是node的灵魂。可是异步的麻烦在于callback,因为我饿每年需要等待完成通知,所以需要callback。
有了 generators,我们可以让代码等。无需callback。可以用generators 在每个异步调用进行中,在调用next()之前暂停执行。还记得yield/next 吗?这是generators的绝活。
我们首先得知道,我们要把异步调用暂停需要用到generator ,而不是典型的函数,所以加个星在函数前:
function* myDelayedMessages() { /* delay 1000 ms and print the result */ /* delay 1200 ms and print the result */ }
加入delay调用。delay需要callback。这个callback需要调用generator.next(),以便继续代码。我们先放一个空的callback:
function* myDelayedMessages() { console.log(delay(1000, function(){})); console.log(delay(1200, function(){})); }
现在代码依然是异步的。加入yield:
function* myDelayedMessages() { console.log(yield delay(1000, function(){})); console.log(yield delay(1200, function(){})); }
又近了一步。但是需要有人告诉generator向前走,走起来。
关键概念在这里:当delay完成时,需要在它的callback内执行点东西,这些东西让generator向前走(调用next)
这个函数,且不管如何实现,我们知道它得叫做resume:
function* myDelayedMessages(resume) { console.log(yield delay(1000, resume)); console.log(yield delay(1200, resume)); }
我们得把定义好的resume传递给 myDelayedMessages
如何实现resume,它又如何知道我们的generator?
给generator 函数加个外套,外套函数的功能就是启动generator,传递写好的resume,第一次拨动generator(调用next),等待resume被调用,在resume内继续拨动generator。这样generator就可以滚动起来了:
function run(generatorFunction) { var generatorItr = generatorFunction(resume); function resume(callbackValue) { generatorItr.next(callbackValue); } generatorItr.next() }
有点烧脑。当年写作名噪一时的“goto statement considered harmful”的作者,看到此代码非得气死。这里没有一行goto,却跳来跳去的比有goto的还难。
注意哦,我们利用了next的第二个特性。resume 就是传递给callback 的函数,它因此接受了delay提供的值,resume传递这个值给 next, 故而 yield 语句返回了我们的异步函数的结果。异步函数的结果于是被console打印出来。就像“倒脱靴”。
代码整合起来:
run(function* myDelayedMessages(resume) { console.log(yield delay(1000, resume)); console.log(yield delay(1200, resume)); }) //...waits 1000ms // > "Slept for 1000" //...waits 1200ms // > "Slept for 1200"
就是这样。我们调用两次delay,按照次序执行,却没有callback的嵌套。如果要调用12次,也还是没有callback嵌套。如果你依然迷惑不解,我们再次分步来一遍》
成功。我们用generator替换了callback。我们这样做到的:
generators替代“callback hell” 是否最佳是可争论的,但是这个练习可以帮助你理解到ES6的generators 和iterators
原文: http://modernweb.com/2014/02/10/replacing-callbacks-with-es6-generator...
fork me from : https://github.com/1000copy/learningnode/blob/master/nodebook/generato...