訪問原文地址javascript
對ES6的generators的介紹分爲3個部分java
第一部分base介紹及使用es6
第二部分基於generators和Promise實現最強大的異步處理邏輯ajax
Generator函數是協程在ES6的實現,用來作異步流程的封裝,最大特色就是能夠交出函數的執行權(即暫停執行)。十分的奇葩,光看語法,簡直認不出這也是JavaScript了。因爲可使用yield語句來暫停異步操做,這讓generators異步編程的代碼,很像同步數據流方法同樣。由於從語法角度來看,generators函數是一個狀態機,封裝了多個內部狀態,經過iterator來分步調用。編程
function與函數名直接的星號:*json
函數體內yield語句promise
function* testGenerator() { yield 'first yield'; yield 'second yield'; return 'last'; } var gen = testGenerator(); console.log(gen.next().value);// first yield // { value: 'first yield', done: false } console.log(gen.next().value);// second yield // { value: 'second yield', done: false } console.log(gen.next().value);// last // { value: 'last', done: true } console.log(gen.next().value);// undefined
for...of循環能夠自動遍歷generators函數的iterator對象,且再也不須要調用next方法。for...of須要檢查iterator對象的done屬性,若是爲true,則結束循環,所以return語句不能被遍歷到app
for (let i of testGenerator) { console.log(i); } // first yield // second yield
yield句自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。異步
function *gen(){ let arr = []; while(true){ arr.push(yield arr); } } var name = gen(); console.log(name.next('first').value);//[] console.log(name.next('second').value);//["second"] console.log(name.next('thrid').value);//["second","thrid"]
須要注意的是,第一次執行next設置參數沒有效果。async
遞歸實現:
function* fib (n, current = 0, next = 1) { if (n === 0) { return 0; } yield current; yield* fib(n - 1, next, current + next); } for (let n of fibonacci()) { if (n > 1000) break; console.log(n); }
注:若是存儲計算結果再過運算,這樣的實現比遞歸方法效率高3倍
function* fibonacci() { let [prev, curr] = [0, 1]; for (;;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { if (n > 1000) break; console.log(n); }
原生的JavaScript對象沒有遍歷接口,沒法使用for...of循環,經過Generator函數爲它加上這個接口,就能夠用了。
function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } } let jane = { first: 'Jane', last: 'Doe' }; for (let [key, value] of objectEntries(jane)) { console.log(`${key}: ${value}`); } // first: Jane // last: Doe
for...of循環
擴展運算符(...)
解構賦值
Array.from方法內部調用的
它們均可以將Generator函數返回的Iterator對象,做爲參數來使用。
function* numbers () { yield 1 yield 2 return 3 } // 擴展運算符 [...numbers()] // [1, 2] // Array.from 方法 Array.from(numbers()) // [1, 2] // 解構賦值 let [x, y] = numbers(); x // 1 y // 2 // for...of 循環 for (let n of numbers()) { console.log(n) } // 1 // 2
generators一個特色就是代碼看上去很是像同步編程的效果
function* test() { yield( "1st" ); yield( "2nd" ); yield( "3rd" ); yield( "4th" ); } var iterator = test(); console.log( "== Start of Line ==" ); console.log( iterator.next().value ); console.log( iterator.next().value ); for ( var line of iterator ) { console.log( line ); } console.log( "== End of Line ==" );
看下輸出,濃濃的同步執行風格。
== Start of Line == 1st 2nd 3rd 4th == End of Line ==
舉例說一個場景,查詢一篇新聞文章的做者信息,流程是:請求最新文章列表->請求某文章相關id->做者id信息
getArticleList(function(articles){ getArticle(articles[0].id, function(article){ getAuthor(article.authorId, function(author){ alert(author.email); }) }) }) function getAuthor(id, callback){ $.ajax(url,{ author: id }).done(function(result){ callback(result); }) } function getArticle(id, callback){ $.ajax(url,{ id: id }).done(function(result){ callback(result); }) } function getArticleList(callback){ $.ajax(url) .done(function(result){ callback(result); }); }
getArticleList() .then(articles => getArticle(articles[0].id)) .then(article => getAuthor(article.authorId)) .then(author => { alert(author.email); }); function getAuthor(id){ return new Promise(function(resolve, reject){ $.ajax({ url: id+'author.json', success: function(data) { resolve(data); } }) }); } function getArticle(id){ return new Promise(function(resolve, reject){ $.ajax({ url: id+'.json', success: function(data) { resolve(data); } }) }); } function getArticleList(){ return new Promise(function(resolve, reject){ $.ajax({ url: 'all.json', success: function(data) { resolve(data); } }) }); }
function* run(){ var articles = yield getArticleList(); var article = yield getArticle(articles[0].id); var author = yield getAuthor(article.authorId); alert(author.email); } var gen = run(); gen.next().value.then(function(r1){ gen.next(r1).value.then(function(r2){ gen.next(r2).value.then(function(r3){ gen.next(r3); console.log("done"); }) }) });
每次都要手動去調用next方法,仍是會讓代碼變得冗長,咱們能夠設計一個專門用來運行generators的方法,並能夠抽象出來,之後就能夠作一個統一的error管理,或者獲取本地數據邏輯的變化。
編譯器的‘傳名調用’實現,將全部的參數放到一個臨時函數中,再將這個臨時函數做爲參數傳入到函數體中。該臨時函數就叫作Thunk函數。
任何函數,只要參數有回調函數,就能寫成Thunk函數的方法。下面就是簡單的Thunk函數轉換器。
//es5 var Thunk = function(fn) { return function() { var args = Array.pototype.silce.call(argumnets); return function (callback) { args.push(callback); return fn.apply(this. args); } } } //es6 var Thunk = function(fn) { return function(...args) { return function(callback) { return fn.call(this, ...args, callback); } } }
一個使用Thunk方法來實現readFile的例子
//正常版本的readFile(多參數) fs.readFile(filename, callback); //Thunk版本的readFile(單參數) var readFileThunk = Thunk(filename); readFileThunk(callback); var Thunk = function(fileName) { return function(callback) { return fs.readFile(fileName, callback); } }
能夠看到,若是咱們經過構建一個基於Thunk方法實現的runGenerators函數,能夠很好的控制咱們的generators運行流程。
function *generator() { var articles = yield getArticleList(); var article = yield getArticle(articles[0].id); var author = yield getAuthor(article.authorId); console.log(author.email); } function runGenerator() { var gen = generator(); function go(result) { if(result.done) return; result.value.then(function(rsp) { go(gen.next(rsp)); }) } go(gen.next()); } runGenerator();