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模塊的實現。