Generators算得上js的一個新概念函數。它看起來像是一個函數,可是能夠暫停執行。從語法上看,有3點關注:html
在node 0.11以上,對node必須加入--harmony 參數,便可支持:前端
$ node --harmony > function *a(){} undefined >
看到undefined就說明支持了。若是不加參數,默認不支持。你會看到node
$ node > function *a(){} ...
聲明一個generator 是這樣的:git
function* ticketGenerator() {}
若是想要 generator 提供一個值並暫停,那麼須要使用yeild 關鍵字。yield 就像 return 同樣返回一個值。和它不一樣的是,yield會暫停函數。es6
function* ticketGenerator() { yield 1; yield 2; yield 3; }
咱們作了一個迭代器,叫作 ticketGenerator. 咱們能夠和它要一個值,而後它返回1,而後暫停。依次返回2,3:github
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 ,不太有用.咱們能夠遞增到無窮。無窮數列來了。web
function* ticketGenerator() { for(var i=0; true; i++) { yield i; } }
再來一遍:npm
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
每次疊加,無窮枚舉。這就有點意思了。app
除了能夠枚舉累加外, 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嵌套。若是你依然疑惑不解,咱們再次分步來一遍》
上面談到的作法,確實能夠把異步改爲同步了。但是並不太完美:好比resume顯得比較突兀,在好比只能在callback返回一個值。不夠通用。
這樣的話,能夠考慮TJ開發的co。連resume的聲明和引用也省掉了。仍是以delay爲例:
var co = require('co'); function delay(time) { return function (callback){ setTimeout(function () {// callback(null,"Slept for "+time); }, time); } } co(function *() { console.log(yield delay(1000)) console.log(yield delay(1200)) })
爲了和co適配,delay須要作些修改,去掉callback,返回一個帶callback的函數,把計算結果經過callback傳遞出去。第一個參數依照node的規矩,留給err。
更絕。怪的不TJ被社區成爲大神。
再來一個。readFile(file,callback),做爲常見的異步函數如何修改?
var co = require('co'); function readFile(file){ return function (callback ){ var fs = require("fs") fs.readFile(file,callback) } } co(function *() { console.log(yield readFile("./app.js")) }) //<Buffer 76 61 72 20 63 ... >
但是,readFile改造這樣過工做,純粹就是boilerplate !因此,有人作了這樣的工做。安裝co-fs,就能夠:
co(function *() { var fs = require("co-fs") var js = yield fs.readFile('./app1.js', 'utf8') var files = yield fs.readdir('.') console.log(js,files) })
node.js真是玩梯雲縱。覺得已經很好了,仍是有人在加入一把火。
因此,值得去npm看看,查找下co-打頭的庫,有1000+個,要不要獨立出去:):
https://www.npmjs.com/search?q=co
成功。咱們用generator替換了callback。咱們這樣作到的:
generators替代「callback hell」 是否最佳是可爭論的,可是這個練習能夠幫助你理解到ES6的generators 和iterators
原文:http://modernweb.com/2014/02/10/replacing-callbacks-with-es6-generator...
參考:
Harmony Generator, yield, ES6, co框架學習 - http://bg.biedalian.com/2013/12/21/harmony-generator.html
Koa, co and coruntine - Harmony - 前端亂燉 - http://www.html-js.com/article/1752
fork me from :https://github.com/1000copy/learningnode/blob/master/nodebook/generato...