摘要:返回值是一個對象,它的第一個屬性是后面表達式的值或者的值第二個屬性表示函數(shù)是否執(zhí)行完成。真正的業(yè)務(wù)邏輯確實是用同步的方式寫的。
開始前
我們從來沒有停止過對javascript語言異步調(diào)用方式的改造,我們一直都想用像java那樣同步的方式去寫異步,盡管Promise可以讓我們將異步回調(diào)添加到then方法中,但是這種調(diào)用方式仍然不那么優(yōu)雅,es6 中新增加了generator,我們可以通過他的特性來實現(xiàn)異步任務(wù)更加優(yōu)雅的書寫方式。
協(xié)程介紹協(xié)程其實和線程,進程是沒有關(guān)系的,它不是操作系統(tǒng)為我們提供的api接口,而是通過編程語言或者匯編語言對程序上下文、程序棧來操作實現(xiàn)的。一個線程里面可以包含多個協(xié)程,線程的調(diào)度是由操作體統(tǒng)來決定的,協(xié)程的調(diào)度是由用戶來決定的。操作系統(tǒng)對其一無所知,因為可以由用戶來調(diào)度,所以用來執(zhí)行協(xié)作式的任務(wù)特別方便。(注意這里是方便,因為能通過協(xié)程解決的問題,通過線程和進程也可以解決,但是復(fù)雜)
Generator介紹Generator 是協(xié)程在es6中的實現(xiàn)。它在es6中是一個函數(shù),這個函數(shù)可以分階段執(zhí)行,也就是說我們可以在這個函數(shù)中的某個位置選擇交出當前線程的執(zhí)行權(quán)限,也可以在當前函數(shù)外面的某個位置選擇將權(quán)限再交回這個函數(shù),讓它繼續(xù)執(zhí)行,這種調(diào)度完全由用戶決定。在es6中協(xié)程函數(shù)是這樣的
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); 僅僅是創(chuàng)建了一個迭代器,函數(shù) gen 里面的內(nèi)容并沒有執(zhí)行函數(shù)體的執(zhí)行時由第一個 g.next(); 開始的 并且將 yield 所在那那條語句執(zhí)行完后就會返回結(jié)果。而后面的語句并沒有執(zhí)行。返回值是一個對象,它的第一個屬性是 yield 后面表達式的值 (p+1或者p+2的值);第二個屬性表示Generator函數(shù)是否執(zhí)行完成。這里我們通過 yield 執(zhí)行權(quán)限交出去,通過 next 將權(quán)限返回。
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 的重要特性由上面的內(nèi)容我們總結(jié) 3 個關(guān)于 Generator 的重要特性
1 通過 yield 交出執(zhí)行權(quán)限,通過 next 返回執(zhí)行權(quán)限
2 調(diào)用 next 會得到一個返回值,這個值里面包含了 yield 后面的表達式的執(zhí)行結(jié)果
3 我們可以通過給 next 傳遞參數(shù),并且可以在 Generator 函數(shù)中通過上面所寫的特殊方式來引用
我們來模擬一個異步函數(shù)
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結(jié)果 url:url, value:10 }; callback(data); }, 1000); } post("http://_ivenj",function(data){ console.log(data.url); // http://_ivenj console.log(data.value); //10 });
對應(yīng)上面的這個異步函數(shù)我想通過 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 畢竟是個異步方法。沒有返回值.如果不能實現(xiàn)這樣的寫法我這半天就是在扯淡,所以通過包裝是可以實現(xiàn)的。
通過以下兩點可以實現(xiàn)以上的書寫方式
(1)我有一篇文章 react 實踐之 redux applyMiddleware方法詳解 中介紹了柯里化(Currying)這篇文章雖然是寫react的但是柯里化是獨立的,這里就要利用柯里化的思想
(2)我們要在回調(diào)中調(diào)用 next 來繼續(xù)執(zhí)行,(這里有人會想不是不用回調(diào)了么,怎么還用,請繼續(xù)看。。。)
我們要對 post 的調(diào)用形式進行包裝
function kPost(url) { return function(callback) { post(url, callback); } }
通過這個包裝,我們就能保證調(diào)用 kPost 就會同步的得到一個返回值
function* gen(url) { var data = yield kPost(url); //1 console.log(data.url); console.log(data.value); } //這里執(zhí)行方式會不同 var g = gen("http://_ivenj"); //啟動任務(wù) var resultG1 = g.next(); var value_resultG1 = resultG1.value; //resultG1.value 一定是一個函數(shù),因為我們包裝了 value_resultG1(function(data){ g.next(data); //通過在異步的回調(diào)中調(diào)用 next 并傳遞值來確保依賴異步結(jié)果的代碼能正確執(zhí)行 });
下面就是整體代碼,是上面的片段組合,請你粘貼到瀏覽器控制臺,或者用node運行,就會看到想要的結(jié)果
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結(jié)果 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); } //這里執(zhí)行方式會不同 var g = gen("http://_ivenj"); //啟動任務(wù) var resultG1 = g.next(); var value_resultG1 = resultG1.value; //resultG1.value 一定是一個函數(shù),因為我們包裝了 value_resultG1(function(data){ g.next(data); });
有人會說,怎么不就是將異步回調(diào)轉(zhuǎn)移出來了么,還要寫回調(diào)。這說明你還沒有真正體會個中之奧妙。我們會發(fā)現(xiàn) 我們寫的異步
value_resultG1(function(data){ g.next(data); });
僅僅是調(diào)用了 next 進行了結(jié)果的傳遞,這里面有共同之處,不管是哪一種異步,我們都只傳遞值。大家的處理都是一樣的。真正的業(yè)務(wù)邏輯確實是用同步的方式寫的。那么,我們可以將共同的地方提取出來,寫一個通用的函數(shù)去執(zhí)行這個傳值操作,這樣,我們完全就告別了異步,再也看不到了,好開心。co.js就是一個這種generator的執(zhí)行庫。使用它是我們只需要將我們的 gen 傳遞給它像這樣 co(gen) 是的就這樣。下面我們自己寫一個 co
Generator執(zhí)行器
function co(taskDef) { //獲取迭代器 類似 java 中的外柄迭代子 var task = taskDef(); //開始任務(wù) var result = task.next(); //調(diào)用next的遞歸函數(shù) function step() { if (!result.done) { //如果generator沒有執(zhí)行完 if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); //向后傳遞當前異步處理結(jié)果 step(); //遞歸執(zhí)行 }); } else { result = task.next(result.value); //如果執(zhí)行完了就傳遞值 step(); //遞歸執(zhí)行 } } } // 啟動遞歸函數(shù) step(); }
通過 co 執(zhí)行的完整代碼
function post(url, callback) { setTimeout(function() { var data = { //模擬異步處理結(jié)果 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(); //開始任務(wù) var result = task.next(); // 調(diào)用next的遞歸函數(shù) function step() { if (!result.done) { //如果generator沒有執(zhí)行完 if (typeof result.value === "function") { result.value(function(err, data) { if (err) { result = task.throw(err); return; } result = task.next(data); //向后傳遞當前異步處理結(jié)果 step(); //遞歸執(zhí)行 }); } else { result = task.next(result.value); //如果執(zhí)行完了就傳遞值 step(); //遞歸執(zhí)行 } } } // 啟動遞歸函數(shù) step(); } co(gen("http://_ivenj")); //調(diào)用方式就是這么簡單
以上代碼執(zhí)行 1s 后會拋出一個異常,并且正確打印{url: "http://_ivenj", value: 10},聰明的你一定知道為什么會拋出異常!!!
到這里已經(jīng)說明白了,并且也說完了,你會想是不是把異步包裝成Promise也可以呢,答案是肯定的,柯里化的思想只是一種實現(xiàn)方式,Promise 也是一種,你可以自己去琢磨,co.js 就是將兩種方式都實現(xiàn)了的一個執(zhí)行器。es7 中從語言層面對 Generator 進行了包裝,在es7 中我們可以使用 async和await更優(yōu)雅的實現(xiàn)類似java的順序書寫方式,async和await 是Generator的語法糖,在es7中內(nèi)置了執(zhí)行器。別人都說是終極方案。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/83918.html
摘要:調(diào)用方法執(zhí)行到后暫停,內(nèi)部環(huán)境被保存,執(zhí)行返回一個對象,為的執(zhí)行結(jié)果,表示迭代器是否完成。當?shù)魍瓿珊?,為,為的值,繼續(xù)執(zhí)行,將為執(zhí)行原理回到開頭的例子,給我們提供了直觀的寫法來處理異步回調(diào),它讓代碼邏輯非常清晰。 編者按:看完本文,你能對ES6的Generator有一個很好的理解,輕松地以同步的方式寫異步代碼,也能初步理解到TJ大神的co框架的原理。 前言:ES6在2015年6月正...
摘要:示例運行函數(shù)彈出彈出函數(shù)接收參數(shù),返回值。其中,返回一個對象,是的返回值,代表函數(shù)是否執(zhí)行完成。 ES6特性介紹(下) ES6新的標準,新的語法特征:1、變量/賦值2、函數(shù)3、數(shù)組/json4、字符串5、面向?qū)ο?、Promise7、generator8、ES7:async/await 《【W(wǎng)eb全棧課程二】ES6特性介紹(上)》見:https://segmentfault.com/a...
摘要:缺點無法取消當處于狀態(tài)時,無法得知目前進展到哪一個階段錯誤不能被生成器什么是函數(shù)是提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同函數(shù)有多種理解角度。 JavaScript的執(zhí)行機制在上篇文章中進行了深入的探討,那么既然是一門單線程語言,如何進行良好體驗的異步編程呢 回調(diào)函數(shù)Callbacks 當程序跑起來時,一般情況下,應(yīng)用程序(application program)會時常通...
摘要:缺點無法取消當處于狀態(tài)時,無法得知目前進展到哪一個階段錯誤不能被生成器什么是函數(shù)是提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同函數(shù)有多種理解角度。 JavaScript的執(zhí)行機制在上篇文章中進行了深入的探討,那么既然是一門單線程語言,如何進行良好體驗的異步編程呢 回調(diào)函數(shù)Callbacks 當程序跑起來時,一般情況下,應(yīng)用程序(application program)會時常通...
閱讀 1293·2021-09-03 10:44
閱讀 677·2019-08-30 13:13
閱讀 2860·2019-08-30 13:11
閱讀 2029·2019-08-30 12:59
閱讀 1110·2019-08-29 15:32
閱讀 1659·2019-08-29 15:25
閱讀 1099·2019-08-29 12:24
閱讀 1366·2019-08-27 10:58