系列文章 -- ES6筆記系列html
接觸過Ajax請求的會遇到過異步調用的問題,爲了保證調用順序的正確性,通常咱們會在回調函數中調用,也有用到一些新的解決方案如Promise相關的技術。ajax
在異步編程中,還有一種經常使用的解決方案,它就是Generator生成器函數。顧名思義,它是一個生成器,它也是一個狀態機,內部擁有值及相關的狀態,生成器返回一個迭代器Iterator對象,咱們能夠經過這個迭代器,手動地遍歷相關的值、狀態,保證正確的執行順序。編程
Generator的聲明方式相似通常的函數聲明,只是多了個*號,而且通常能夠在函數內看到yield關鍵字數組
function* showWords() { yield 'one'; yield 'two'; return 'three'; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: "two"} show.next() // {done: true, value: "three"} show.next() // {done: true, value: undefined}
如上代碼,定義了一個showWords的生成器函數,調用以後返回了一個迭代器對象(即show)promise
調用next方法後,函數內執行第一條yield語句,輸出當前的狀態done(迭代器是否遍歷完成)以及相應值(通常爲yield關鍵字後面的運算結果)網絡
每調用一次next,則執行一次yield語句,並在該處暫停,return完成以後,就退出了生成器函數,後續若是還有yield操做就再也不執行了異步
有時候,咱們會看到yield以後跟了一個*號,它是什麼,有什麼用呢?異步編程
相似於生成器前面的*號,yield後面的星號也跟生成器有關,舉個大栗子:函數
function* showWords() { yield 'one'; yield showNumbers(); return 'three'; } function* showNumbers() { yield 10 + 1; yield 12; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: showNumbers} show.next() // {done: true, value: "three"} show.next() // {done: true, value: undefined}
增添了一個生成器函數,咱們想在showWords中調用一次,簡單的 yield showNumbers()以後發現並無執行函數裏面的yield 10+1url
由於yield只能原封不動地返回右邊運算後值,但如今的showNumbers()不是通常的函數調用,返回的是迭代器對象
因此換個yield* 讓它自動遍歷進該對象
function* showWords() { yield 'one'; yield* showNumbers(); return 'three'; } function* showNumbers() { yield 10 + 1; yield 12; } var show = showWords(); show.next() // {done: false, value: "one"} show.next() // {done: false, value: 11} show.next() // {done: false, value: 12} show.next() // {done: true, value: "three"}
要注意的是,這yield和yield* 只能在generator函數內部使用,通常的函數內使用會報錯
function showWords() { yield 'one'; // Uncaught SyntaxError: Unexpected string }
雖然換成yield*不會直接報錯,但使用的時候仍是會有問題,由於’one'字符串中沒有Iterator接口,沒有yield提供遍歷
function showWords() { yield* 'one'; } var show = showWords(); show.next() // Uncaught ReferenceError: yield is not defined
在爬蟲開發中,咱們經常須要請求多個地址,爲了保證順序,引入Promise對象和Generator生成器函數,看這個簡單的栗子:
var urls = ['url1', 'url2', 'url3']; function* request(urls) { urls.forEach(function(url) { yield req(url); }); // for (var i = 0, j = urls.length; i < j; ++i) { // yield req(urls[i]); // } } var r = request(urls); r.next(); function req(url) { var p = new Promise(function(resolve, reject) { $.get(url, function(rs) { resolve(rs); }); }); p.then(function() { r.next(); }).catch(function() { }); }
上述代碼中forEach遍歷url數組,匿名函數內部不能使用yield關鍵字,改換成註釋中的for循環就好了
參數值有注入的功能,可改變上一個yield的返回值,如
function* showNumbers() { var one = yield 1; var two = yield 2 * one; yield 3 * two; } var show = showNumbers(); show.next().value // 1 show.next().value // NaN show.next(2).value // 6
第一次調用next以後返回值one爲1,但在第二次調用next的時候one實際上是undefined的,由於generator不會自動保存相應變量值,咱們須要手動的指定,這時two值爲NaN,在第三次調用next的時候執行到yield 3 * two,經過傳參將上次yield返回值two設爲2,獲得結果
另外一個栗子:
因爲ajax請求涉及到網絡,很差處理,這裏用了setTimeout模擬ajax的請求返回,按順序進行,並傳遞每次返回的數據
1 var urls = ['url1', 'url2', 'url3']; 2 3 function* request(urls) { 4 var data; 5 6 for (var i = 0, j = urls.length; i < j; ++i) { 7 data = yield req(urls[i], data); 8 } 9 } 10 11 var r = request(urls); 12 r.next(); 13 14 function log(url, data, cb) { 15 setTimeout(function() { 16 cb(url); 17 }, 1000); 18 19 } 20 21 22 function req(url, data) { 23 var p = new Promise(function(resolve, reject) { 24 log(url, data, function(rs) { 25 if (!rs) { 26 reject(); 27 } else { 28 resolve(rs); 29 } 30 }); 31 }); 32 33 p.then(function(data) { 34 console.log(data); 35 r.next(data); 36 }).catch(function() { 37 38 }); 39 }
達到了按順序請求三個地址的效果,初始直接r.next()無參數,後續經過r.next(data)將data數據傳入
注意代碼的第16行,這裏參數用了url變量,是爲了和data數據作對比
由於初始next()沒有參數,如果直接將url換成data的話,就會由於promise對象的數據判斷 !rs == undefined 而reject
因此將第16行換成 cb(data || url);
經過模擬的ajax輸出,可瞭解到next的傳參值,第一次在log輸出的是 url = 'url1'值,後續將data = 'url1'傳入req請求,在log中輸出 data = 'url1'值
除了使用.next()方法遍歷迭代器對象外,經過ES6提供的新循環方式for...of也可遍歷,但與next不一樣的是,它會忽略return返回的值,如
function* showNumbers() { yield 1; yield 2; return 3; } var show = showNumbers(); for (var n of show) { console.log(n) // 1 2 }
此外,處理for...of循環,具備調用迭代器接口的方法方式也可遍歷生成器函數,如擴展運算符...的使用
function* showNumbers() { yield 1; yield 2; return 3; } var show = showNumbers(); [...show] // [1, 2, length: 2]
更多使用可參考 MDN - Generator