摘要:返回值是一個對象,它的第一個屬性是后面表達式的值或者的值第二個屬性表示函數是否執行完成。真正的業務邏輯確實是用同步的方式寫的。
開始前
我們從來沒有停止過對javascript語言異步調用方式的改造,我們一直都想用像java那樣同步的方式去寫異步,盡管Promise可以讓我們將異步回調添加到then方法中,但是這種調用方式仍然不那么優雅,es6 中新增加了generator,我們可以通過他的特性來實現異步任務更加優雅的書寫方式。
協程介紹協程其實和線程,進程是沒有關系的,它不是操作系統為我們提供的api接口,而是通過編程語言或者匯編語言對程序上下文、程序棧來操作實現的。一個線程里面可以包含多個協程,線程的調度是由操作體統來決定的,協程的調度是由用戶來決定的。操作系統對其一無所知,因為可以由用戶來調度,所以用來執行協作式的任務特別方便。(注意這里是方便,因為能通過協程解決的問題,通過線程和進程也可以解決,但是復雜)
Generator介紹Generator 是協程在es6中的實現。它在es6中是一個函數,這個函數可以分階段執行,也就是說我們可以在這個函數中的某個位置選擇交出當前線程的執行權限,也可以在當前函數外面的某個位置選擇將權限再交回這個函數,讓它繼續執行,這種調度完全由用戶決定。在es6中協程函數是這樣的
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 將權限返回。
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
Generator 的重要特性由上面的內容我們總結 3 個關于 Generator 的重要特性
1 通過 yield 交出執行權限,通過 next 返回執行權限
2 調用 next 會得到一個返回值,這個值里面包含了 yield 后面的表達式的執行結果
3 我們可以通過給 next 傳遞參數,并且可以在 Generator 函數中通過上面所寫的特殊方式來引用
我們來模擬一個異步函數
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 來這樣用
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中內置了執行器。別人都說是終極方案。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83918.html
摘要:調用方法執行到后暫停,內部環境被保存,執行返回一個對象,為的執行結果,表示迭代器是否完成。當迭代器完成后,為,為的值,繼續執行,將為執行原理回到開頭的例子,給我們提供了直觀的寫法來處理異步回調,它讓代碼邏輯非常清晰。 編者按:看完本文,你能對ES6的Generator有一個很好的理解,輕松地以同步的方式寫異步代碼,也能初步理解到TJ大神的co框架的原理。 前言:ES6在2015年6月正...
摘要:示例運行函數彈出彈出函數接收參數,返回值。其中,返回一個對象,是的返回值,代表函數是否執行完成。 ES6特性介紹(下) ES6新的標準,新的語法特征:1、變量/賦值2、函數3、數組/json4、字符串5、面向對象6、Promise7、generator8、ES7:async/await 《【Web全棧課程二】ES6特性介紹(上)》見:https://segmentfault.com/a...
摘要:缺點無法取消當處于狀態時,無法得知目前進展到哪一個階段錯誤不能被生成器什么是函數是提供的一種異步編程解決方案,語法行為與傳統函數完全不同函數有多種理解角度。 JavaScript的執行機制在上篇文章中進行了深入的探討,那么既然是一門單線程語言,如何進行良好體驗的異步編程呢 回調函數Callbacks 當程序跑起來時,一般情況下,應用程序(application program)會時常通...
摘要:缺點無法取消當處于狀態時,無法得知目前進展到哪一個階段錯誤不能被生成器什么是函數是提供的一種異步編程解決方案,語法行為與傳統函數完全不同函數有多種理解角度。 JavaScript的執行機制在上篇文章中進行了深入的探討,那么既然是一門單線程語言,如何進行良好體驗的異步編程呢 回調函數Callbacks 當程序跑起來時,一般情況下,應用程序(application program)會時常通...
閱讀 1207·2021-09-03 10:44
閱讀 604·2019-08-30 13:13
閱讀 2796·2019-08-30 13:11
閱讀 1967·2019-08-30 12:59
閱讀 1034·2019-08-29 15:32
閱讀 1595·2019-08-29 15:25
閱讀 987·2019-08-29 12:24
閱讀 1277·2019-08-27 10:58