咱們歷來沒有中止過對javascript
語言異步調用方式的改造,咱們一直都想用像java
那樣同步的方式去寫異步,儘管Promise
可讓咱們將異步回調添加到then
方法中,可是這種調用方式仍然不那麼優雅,es6
中新增長了generator
,咱們能夠經過他的特性來實現異步任務更加優雅的書寫方式。javascript
協程其實和線程,進程是沒有關係的,它不是操做系統爲咱們提供的api
接口,而是經過編程語言或者彙編語言對程序上下文、程序棧來操做實現的。一個線程裏面能夠包含多個協程,線程的調度是由操做體統來決定的,協程的調度是由用戶來決定的。操做系統對其一無所知,由於能夠由用戶來調度,因此用來執行協做式的任務特別方便。(注意這裏是方便,由於能經過協程解決的問題,經過線程和進程也能夠解決,可是複雜)java
Generator
介紹Generator
是協程在es6
中的實現。它在es6
中是一個函數,這個函數能夠分階段執行,也就是說咱們能夠在這個函數中的某個位置選擇交出當前線程的執行權限,也能夠在當前函數外面的某個位置選擇將權限再交回這個函數,讓它繼續執行,這種調度徹底由用戶決定。在es6
中協程函數是這樣的node
function* gen(p) { var a = yield p + 1; //1 var b = yield p + 2; //2 return b; //3 } var g = gen(1); g.next(); //{value: 2, done: false} g.next(); //{value: 3, done: false} g.next(); //{value: undefined, done: true}
經過 var g = gen(1);
僅僅是建立了一個迭代器,函數 gen
裏面的內容並無執行函數體的執行時由第一個 g.next();
開始的 而且將 yield
所在那那條語句執行完後就會返回結果。然後面的語句並無執行。返回值是一個對象,它的第一個屬性是 yield 後面表達式的值 (p+1
或者p+2
的值);第二個屬性表示Generator函數是否執行完成。這裏咱們經過 yield
執行權限交出去,經過 next
將權限返回。react
function* gen(p) { var a = yield p + 1; //1 var b = yield a + 1; //2 注意這裏是用到了 a return b; } var g = gen(1); g.next(); //{value: 2, done: false} g.next(); //{value: NaN, done: false} 這裏的值是 NaN g.next(); //{value: undefined, done: true} g.next(); //{value: 2, done: false} g.next(2); //{value: 3, done: false} g.next(6); //{value: 6, done: true}
注意這裏 //1 處 //2 處 var a = yield p + 1;
這條賦值語句中 a
的值並非 p + 1
的值。這條語句只是一種寫法,這裏 a
的值是咱們在第二個 next
中傳入的 2
這個很重要 b
的值也是咱們在第三個 next
中傳入的 6
es6
Generator
的重要特性由上面的內容咱們總結 3 個關於 Generator 的重要特性編程
1 經過 yield
交出執行權限,經過 next
返回執行權限
2 調用 next
會獲得一個返回值,這個值裏面包含了 yield
後面的表達式的執行結果
3 咱們能夠經過給 next
傳遞參數,而且能夠在 Generator 函數中經過上面所寫的特殊方式來引用redux
Generator
的特性來實現異步代碼的同步書寫咱們來模擬一個異步函數segmentfault
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結果 url:url, value:10 }; callback(data); }, 1000); } post('http://_ivenj',function(data){ console.log(data.url); // http://_ivenj console.log(data.value); //10 });
對應上面的這個異步函數我想經過 Generator 來這樣用api
function* gen(url) { var data = yield post(url); //1 console.log(data.url); console.log(data.value); } var g = gen('http://_ivenj'); var resultG = g.next(); g.next(resultG.value);
是的,這樣寫漂亮多了,很像 java
的同步寫法。不一樣之處就是多了個 yield
和 *
,這個無傷大雅。固然以上這樣用確定是不行的。由於 post
畢竟是個異步方法。沒有返回值.若是不能實現這樣的寫法我這半天就是在扯淡,因此經過包裝是能夠實現的。瀏覽器
經過如下兩點能夠實現以上的書寫方式
(1)我有一篇文章 react 實踐之 redux applyMiddleware方法詳解 中介紹了柯里化(Currying)這篇文章雖然是寫react的可是柯里化是獨立的,這裏就要利用柯里化的思想
(2)咱們要在回調中調用 next
來繼續執行,(這裏有人會想不是不用回調了麼,怎麼還用,請繼續看。。。)
咱們要對 post
的調用形式進行包裝
function kPost(url) { return function(callback) { post(url, callback); } }
經過這個包裝,咱們就能保證調用 kPost
就會同步的獲得一個返回值
function* gen(url) { var data = yield kPost(url); //1 console.log(data.url); console.log(data.value); } //這裏執行方式會不一樣 var g = gen('http://_ivenj'); //啓動任務 var resultG1 = g.next(); var value_resultG1 = resultG1.value; //resultG1.value 必定是一個函數,由於咱們包裝了 value_resultG1(function(data){ g.next(data); //經過在異步的回調中調用 next 並傳遞值來確保依賴異步結果的代碼能正確執行 });
下面就是總體代碼,是上面的片斷組合,請你粘貼到瀏覽器控制檯,或者用node運行,就會看到想要的結果
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結果 url:url, value:10 }; callback(data); }, 1000); } function kPost(url) { return function(callback) { post(url, callback); } } function* gen(url) { var data = yield kPost(url); //1 console.log(data.url); console.log(data.value); } //這裏執行方式會不一樣 var g = gen('http://_ivenj'); //啓動任務 var resultG1 = g.next(); var value_resultG1 = resultG1.value; //resultG1.value 必定是一個函數,由於咱們包裝了 value_resultG1(function(data){ g.next(data); });
有人會說,怎麼不就是將異步回調轉移出來了麼,還要寫回調。這說明你尚未真正體會個中之奧妙。咱們會發現 咱們寫的異步
value_resultG1(function(data){ g.next(data); });
僅僅是調用了 next
進行告終果的傳遞,這裏面有共同之處,不論是哪種異步,咱們都只傳遞值。你們的處理都是同樣的。真正的業務邏輯確實是用同步的方式寫的。那麼,咱們能夠將共同的地方提取出來,寫一個通用的函數去執行這個傳值操做,這樣,咱們徹底就告別了異步,再也看不到了,好開心。co.js
就是一個這種generator
的執行庫。使用它是咱們只須要將咱們的 gen 傳遞給它像這樣 co(gen)
是的就這樣。下面咱們本身寫一個 co
Generator執行器
function co(taskDef) { //獲取迭代器 相似 java 中的外柄迭代子 var task = taskDef(); //開始任務 var result = task.next(); //調用next的遞歸函數 function step() { if (!result.done) { //若是generator沒有執行完 if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); //向後傳遞當前異步處理結果 step(); //遞歸執行 }); } else { result = task.next(result.value); //若是執行完了就傳遞值 step(); //遞歸執行 } } } // 啓動遞歸函數 step(); }
經過 co 執行的完整代碼
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結果 url:url, value:10 }; callback(data); }, 1000); } function kPost(url) { return function(callback) { post(url, callback); } } function gen(url) { return function* () { var data = yield kPost(url); //1 console.log(data.url); console.log(data.value); } } function co(taskDef) { var task = taskDef(); //開始任務 var result = task.next(); // 調用next的遞歸函數 function step() { if (!result.done) { //若是generator沒有執行完 if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); //向後傳遞當前異步處理結果 step(); //遞歸執行 }); } else { result = task.next(result.value); //若是執行完了就傳遞值 step(); //遞歸執行 } } } // 啓動遞歸函數 step(); } co(gen('http://_ivenj')); //調用方式就是這麼簡單
以上代碼執行 1s 後會拋出一個異常,而且正確打印{url: "http://_ivenj", value: 10},聰明的你必定知道爲何會拋出異常!!!
到這裏已經說明白了,而且也說完了,你會想是否是把異步包裝成Promise
也能夠呢,答案是確定的,柯里化的思想只是一種實現方式,Promise
也是一種,你能夠本身去琢磨,co.js
就是將兩種方式都實現了的一個執行器。es7
中從語言層面對 Generator
進行了包裝,在es7
中咱們可使用 async
和await
更優雅的實現相似java的順序書寫方式,async
和await
是Generator
的語法糖,在es7
中內置了執行器。別人都說是終極方案。