摘要:如果你已經(jīng)理解基礎(chǔ)可以直接跳過(guò)這部分和語(yǔ)法部分,直接看深入理解的部分。的參數(shù)可以傳入一個(gè)參數(shù),來(lái)作為上一次的表達(dá)式的返回值,就像我們上面說(shuō)的會(huì)讓等于。
這篇文章旨在幫你真正了解Generator,文章較長(zhǎng),不過(guò)如果能花時(shí)間耐心看完,相信你已經(jīng)能夠完全理解generator為什么要用generator
在前端開(kāi)發(fā)過(guò)程中我們經(jīng)常需要先請(qǐng)求后端的數(shù)據(jù),再用拿來(lái)的數(shù)據(jù)進(jìn)行使用網(wǎng)頁(yè)頁(yè)面渲染等操作,然而請(qǐng)求數(shù)據(jù)是一個(gè)異步操作,而我們的頁(yè)面渲染又是同步操作,這里ES6中的generator就能發(fā)揮它的作用,使用它可以像寫(xiě)同步代碼一樣寫(xiě)異步代碼。下面是一個(gè)例子,先忽略下面的寫(xiě)法,后面會(huì)詳細(xì)說(shuō)明。如果你已經(jīng)理解generator基礎(chǔ)可以直接跳過(guò)這部分和語(yǔ)法部分,直接看深入理解的部分。
function *foo() { // 請(qǐng)求數(shù)據(jù) var data = yield makeAjax("http://www.example.com"); render(data); }
在等待數(shù)據(jù)的過(guò)程中會(huì)繼續(xù)執(zhí)行其他部分的代碼,直到數(shù)據(jù)返回才會(huì)繼續(xù)執(zhí)行foo中后面的代碼,這是怎么實(shí)現(xiàn)的那?我們都知道js是單線程的,就是說(shuō)我們不可能同時(shí)執(zhí)行兩段代碼,要實(shí)現(xiàn)這種效果,我們先來(lái)猜想下,我們來(lái)假設(shè)有一個(gè)“王杖”(指代cpu的執(zhí)行權(quán)),誰(shuí)拿到這個(gè)“王杖”,誰(shuí)就可以做自己想做的事,現(xiàn)在代碼執(zhí)行到foo我們現(xiàn)在拿著“王杖”然后向服務(wù)器請(qǐng)求數(shù)據(jù),現(xiàn)在數(shù)據(jù)還沒(méi)有返回,我們不能干等著。作為王我們有著高尚的馬克思主義思想,我們先把自己的權(quán)利交出去,讓下一個(gè)需要用的人先用著,當(dāng)然前提是要他們約定好一會(huì)兒有需要,再把“王杖”還給我們。等數(shù)據(jù)返回之后,我們?cè)侔盐覀兊摹巴跽取币貋?lái),就可以繼續(xù)做我們想做的事情了。
如果你理解了這個(gè)過(guò)程,那么恭喜你,你已經(jīng)基本理解了generator的運(yùn)行機(jī)制,我這么比喻雖然有些過(guò)程不是很貼切,但基本是這么個(gè)思路。更多的東西還是向下看吧。
在用generator之前,我們首先要了解它的語(yǔ)法。在上面也看到過(guò),它跟函數(shù)聲明很像,但后面有多了個(gè)*號(hào),就是function *foo() { },當(dāng)然也可以這么寫(xiě)function* foo() { }。這里兩種寫(xiě)法沒(méi)有任何區(qū)別,全看個(gè)人習(xí)慣,這篇文章里我會(huì)用第一種語(yǔ)法?,F(xiàn)在我們按這種語(yǔ)法聲明一個(gè)generator函數(shù),供后面使用。
function *foo() { }yield
到目前為止,我們還什么也干不了,因?yàn)槲覀冞€缺少了一個(gè)重要的老伙計(jì)yield。yield翻譯成漢語(yǔ)是產(chǎn)生的意思。yield會(huì)讓我們跟在后面的表達(dá)式執(zhí)行,然后交出自己的控制權(quán),停在這里,直到我們調(diào)用next()才會(huì)繼續(xù)向下執(zhí)行。這里新出現(xiàn)了next我們先跳過(guò),先說(shuō)說(shuō)generator怎么執(zhí)行。先看一個(gè)例子。
function *foo() { var a = yield 1 + 1; var b = yield 2 + a; console.log(b); } var it = foo(); it.next(); it.next(2); it.next(4);
下面我們來(lái)逐步分析,首先我們定義了一個(gè)generator函數(shù)foo,然后我們執(zhí)行它foo(),這里跟普通函數(shù)不同的是,它執(zhí)行完之后返回的是一個(gè)迭代器,等著我們自己卻調(diào)用一個(gè)又一個(gè)的yield。怎么調(diào)用那,這就用到我們前面提到的next了,它能夠讓迭代器一個(gè)一個(gè)的執(zhí)行。好,現(xiàn)在我們調(diào)用第一個(gè)it.next(),函數(shù)會(huì)從頭開(kāi)始執(zhí)行,然后執(zhí)行到了第一個(gè)yield,它首先計(jì)算了1 + 1,嗯,然后停了下來(lái)。然后我們調(diào)用第二個(gè)it.next(2),注意我這里傳入了一個(gè)2作為next函數(shù)的參數(shù),這個(gè)2傳給了a作為它的值,你可能還有很多其他的疑問(wèn),我們?cè)敿?xì)的后面再說(shuō)。接著來(lái),我們的it.next(2)執(zhí)行到了第二個(gè)yield,并計(jì)算了2 + a由于a是2所以就變成了2 + 2。第三步我們?cè)僬{(diào)用it.next(4),過(guò)程跟上一步相同,我們把b賦值為4繼續(xù)向下執(zhí)行,執(zhí)行到了最后打印出我們的b為4。這就是generator執(zhí)行的全部的過(guò)程了。現(xiàn)在弄明白了yield跟next的作用,回到剛才的問(wèn)題,你可能要問(wèn),為什么要在next中傳入2和4,這里是為了方便理解,我手動(dòng)計(jì)算了1 + 1和2 + 2的值,那么程序自己計(jì)算的值在哪里?是next函數(shù)的返回值嗎,帶著這個(gè)疑問(wèn),我們來(lái)看下面一部分。
next next的參數(shù)next可以傳入一個(gè)參數(shù),來(lái)作為上一次yield的表達(dá)式的返回值,就像我們上面說(shuō)的it.next(2)會(huì)讓a等于2。當(dāng)然第一次執(zhí)行next也可以傳入一個(gè)參數(shù),但由于它沒(méi)有上一次yield所以沒(méi)有任何東西能夠接受它,會(huì)被忽略掉,所以沒(méi)有什么意義。
next的返回值在這部分我們說(shuō)說(shuō)next返回值,廢話不多說(shuō),我們先打印出來(lái),看看它到底是什么,你可以自己執(zhí)行一下,也可以直接看我執(zhí)行的結(jié)果。
function *foo() { var a = yield 1 + 1; var b = yield 2 + a; console.log(b); } var it = foo(); console.log(it.next()); console.log(it.next(2)); console.log(it.next(4));
執(zhí)行結(jié)果:
{ value: 2, done: false } { value: 4, done: false } 4 { value: undefined, done: true }
看到這里你會(huì)發(fā)現(xiàn),yield后面的表達(dá)式執(zhí)行的結(jié)果確實(shí)返回了,不過(guò)是在返回值的value字段中,那還有done字段使用來(lái)做什么用的那。其實(shí)這里的done是用來(lái)指示我們的迭代器,就是例子中的it是否執(zhí)行完了,仔細(xì)觀察你會(huì)發(fā)現(xiàn)最后一個(gè)it.next(4)返回值是done: true的,前面的都是false,那么最后一個(gè)打印值的undefined又是什么那,因?yàn)槲覀兒竺鏇](méi)有yield了,所以這里沒(méi)有被計(jì)算出值,那么怎么讓最后一個(gè)有值那,很簡(jiǎn)單加個(gè)return。我們改寫(xiě)下上面的例子。
function *foo() { var a = yield 1 + 1; var b = yield 2 + a; return b + 1; } var it = foo(); console.log(it.next()); console.log(it.next(2)); console.log(it.next(4));
執(zhí)行結(jié)果:
{ value: 2, done: false } { value: 4, done: false } { value: 5, done: true }
最后的next的value的值就是最終return返回的值。到這里我們就不再需要手動(dòng)計(jì)算我們的值了,我們?cè)诟膶?xiě)下我們的例子。
function *foo() { var a = yield 1 + 1; var b = yield 2 + a; return b + 1; } var it = foo(); var value1 = it.next().value; var value2 = it.next(value1).value; console.log(it.next(value2));
大功告成!這些基本上就完成了generator的基礎(chǔ)部分。但是還有更多深入的東西需要我們進(jìn)一步挖掘,看下去,相信你會(huì)有收獲的。
深入理解前兩部分我們學(xué)習(xí)了為什么要用generator以及generator的語(yǔ)法,這些都是基礎(chǔ),下面我們來(lái)看點(diǎn)不一樣的東西,老規(guī)矩先帶著問(wèn)題才能更有目的性的看,這里先提出幾個(gè)問(wèn)題:
怎樣在異步代碼中使用,上面的例子都是同步的啊
如果出現(xiàn)錯(cuò)誤要怎么進(jìn)行錯(cuò)誤的處理
一個(gè)個(gè)調(diào)用next太麻煩了,能不能循環(huán)執(zhí)行或者自動(dòng)執(zhí)行那
迭代器進(jìn)行下面所有的部分之前我們先說(shuō)一說(shuō)迭代器,看到現(xiàn)在,我們都知道generator函數(shù)執(zhí)行完返回的是一個(gè)迭代器。在ES6中同樣提供了一種新的迭代方式for...of,for...of可以幫助我們直接迭代出每個(gè)的值,在數(shù)組中它像這樣。
for (var i of ["a", "b", "c"]) { console.log(i); } // 輸出結(jié)果 // a // b // c
下面我們用我們的generator迭代器試試
function *foo() { yield 1; yield 2; yield 3; return 4; } // 獲取迭代器 var it = foo(); for(var i of it) { console.log(i); } // 輸出結(jié)果 // 1 // 2 // 3
現(xiàn)在我們發(fā)現(xiàn)for...of會(huì)直接取出我們每一次計(jì)算返回的值,直到done: true。這里注意,我們的4沒(méi)有打印出來(lái),說(shuō)明for...of迭代,是不包括done為true的時(shí)候的值的。
下面我們提一個(gè)新的問(wèn)題,如果在generator中執(zhí)行generator會(huì)怎么樣?這里我們先認(rèn)識(shí)一個(gè)新的語(yǔ)法yield *,這個(gè)語(yǔ)法可以讓我們?cè)?b>yield跟一個(gè)generator執(zhí)行器,當(dāng)yield遇到一個(gè)新的generator需要執(zhí)行,它會(huì)先將這個(gè)新的generator執(zhí)行完,再繼續(xù)執(zhí)行我們當(dāng)前的generator。這樣說(shuō)可能不太好理解,我們看代碼。
function *foo() { yield 2; yield 3; yield 4; } function * bar() { yield 1; yield *foo(); yield 5; } for ( var v of bar()) { console.log(v); }
這里有兩個(gè)generator我們?cè)?b>bar中執(zhí)行了foo,我們使用了yield *來(lái)執(zhí)行foo,這里的執(zhí)行順序會(huì)是yield 1,然后遇到foo進(jìn)入foo中,繼續(xù)執(zhí)行foo中的yield 2直到foo執(zhí)行完畢。然后繼續(xù)回到bar中執(zhí)行yield 5所以最后的執(zhí)行結(jié)果是:
1 2 3 4 5異步請(qǐng)求
我們上面的例子一直都是同步的,但實(shí)際上我們的應(yīng)用是在異步中,我們現(xiàn)在來(lái)看看異步中怎么應(yīng)用。
function request(url) { makeAjaxCall(url, function(response) { it.next(response); }) } function *foo() { var data = yield request("http://api.example.com"); console.log(JSON.parse(data)); } var it = foo(); it.next();
這里又回到一開(kāi)頭說(shuō)的那個(gè)例子,異步請(qǐng)求在執(zhí)行到yield的時(shí)候交出控制權(quán),然后等數(shù)據(jù)回調(diào)成功后在回調(diào)中交回控制權(quán)。所以像同步一樣寫(xiě)異步代碼并不是說(shuō)真的變同步了,只是異步回調(diào)的過(guò)程被封裝了,從外面看不到而已。
錯(cuò)誤處理我們都知道在js中我們使用try...catch來(lái)處理錯(cuò)誤,在generator中類似,如果在generator內(nèi)發(fā)生錯(cuò)誤,如果內(nèi)部能處理,就在內(nèi)部處理,不能處理就繼續(xù)向外冒泡,直到能夠處理錯(cuò)誤或最后一層。
內(nèi)部處理錯(cuò)誤:
// 內(nèi)部處理 function *foo() { try { yield Number(4).toUpperCase(); } catch(e) { console.log("error in"); } } var it = foo(); it.next(); // 運(yùn)行結(jié)果:error in
外部處理錯(cuò)誤:
// 外部處理 function *foo() { yield Number(4).toUpperCase(); } var it = foo(); try { it.next(); } catch(e) { console.log("error out"); } // 運(yùn)行結(jié)果:error out
在generator的錯(cuò)誤處理中還有一個(gè)特殊的地方,它的迭代器有一個(gè)throw方法,能夠?qū)㈠e(cuò)誤丟回generator中,在它暫停的地方報(bào)錯(cuò),再往后就跟上面一樣了,如果內(nèi)部能處理則內(nèi)部處理,不能內(nèi)部處理則繼續(xù)冒泡。
內(nèi)部處理結(jié)果:
function *foo() { try { yield 1; } catch(e) { console.log("error", e); } yield 2; yield 3; } var it = foo(); it.next(); it.throw("oh no!"); // 運(yùn)行結(jié)果:error oh no!
外部處理結(jié)果:
function *foo() { yield 1; yield 2; yield 3; } var it = foo(); it.next(); try { it.throw("oh no!"); } catch (e) { console.log("error", e); } // 運(yùn)行結(jié)果:error oh no!
根據(jù)測(cè)試,發(fā)現(xiàn)迭代器的throw也算作一次迭代,測(cè)試代碼如下:
function *foo() { try { yield 1; yield 2; } catch (e) { console.log("error", e); } yield 3; } var it = foo(); console.log(it.next()); it.throw("oh no!"); console.log(it.next()); // 運(yùn)行結(jié)果 // { value: 1, done: false } // error oh no! // { value: undefined, done: true }
當(dāng)用throw丟回錯(cuò)誤的時(shí)候,除了try中的語(yǔ)句,迭代器迭代掉了yield 3下次再迭代就是,就是最后結(jié)束的值了。錯(cuò)誤處理到這里就沒(méi)有了,就這么點(diǎn)東西^_^。
自動(dòng)運(yùn)行generator能不能自動(dòng)運(yùn)行?當(dāng)然能,并且有很多這樣的庫(kù),這里我們先自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的。
function run(g) { var it = g(); // 利用遞歸進(jìn)行迭代 (function iterator(val) { var ret = it.next(val); // 如果沒(méi)有結(jié)束 if(!ret.done) { // 判斷promise if(typeof ret.value === "object" && "then" in ret.value) { ret.value.then(iterator); } else { iterator(ret.value); } } })(); }
這樣我們就能自動(dòng)處理運(yùn)行我們的generator了,當(dāng)然我們這個(gè)很簡(jiǎn)單,沒(méi)有任何錯(cuò)誤處理,如何讓多個(gè)generator同時(shí)運(yùn)行,這其中涉及到如何進(jìn)行控制權(quán)的轉(zhuǎn)換問(wèn)題。我寫(xiě)了一個(gè)簡(jiǎn)單的執(zhí)行器Fo,其中包含了Kyle Simpson大神的一個(gè)ping-pong的例子,感興趣的可以看下這里是傳送門(mén),當(dāng)然能順手star一下就更好了,see you next article ~O(∩_∩)O~。
參考鏈接The Basics Of ES6 Generators
Diving Deeper With ES6 Generators
Going Async With ES6 Generators
Getting Concurrent With ES6 Generators
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/107428.html
摘要:同時(shí),迭代器有一個(gè)方法來(lái)向函數(shù)中暫停處拋出一個(gè)錯(cuò)誤,該錯(cuò)誤依然可以通過(guò)函數(shù)內(nèi)部的模塊進(jìn)行捕獲處理。 本文翻譯自:Diving Deeper With ES6 Generators 由于個(gè)人能力有限,翻譯中難免有紕漏和錯(cuò)誤,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...
摘要:形式非必須,也非必須調(diào)用把用函數(shù)表示在調(diào)用的時(shí)候用函數(shù)代碼更加同步化三是什么異步操作的終極解決方案寫(xiě)法四總結(jié)不管用還是用還是用,都保證你寫(xiě)的的返回值是一個(gè)對(duì)象 一、promise入門(mén) 1. Promise對(duì)象是什么 回調(diào)函數(shù)的另一種原生實(shí)現(xiàn),比之前回調(diào)函數(shù)的寫(xiě)法機(jī)構(gòu)清晰,功能強(qiáng)大, 2.以前回調(diào)這么寫(xiě) function a(fn){ let h = 1; setTime...
摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...
摘要:一步,一步前進(jìn)一步深入淺出之生成器。本人對(duì)生成器的印象是語(yǔ)法難以理解,又沒(méi)有什么實(shí)際的應(yīng)用場(chǎng)景。為啥要學(xué)習(xí)一下呢可能未來(lái)某些高級(jí)的業(yè)務(wù)會(huì)用到,還有萬(wàn)一面試官問(wèn)的話,我得能侃幾句,顯得我牛 一步,一步前進(jìn)の一步 ES6深入淺出之Generator生成器。本人對(duì)生成器的印象是語(yǔ)法難以理解,又沒(méi)有什么實(shí)際的應(yīng)用場(chǎng)景。為啥要學(xué)習(xí)一下呢?可能未來(lái)某些高級(jí)的業(yè)務(wù)會(huì)用到,還有萬(wàn)一面試官問(wèn)的話,我得能...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
閱讀 1695·2021-10-27 14:13
閱讀 1968·2021-10-11 10:59
閱讀 3452·2021-09-24 10:26
閱讀 1987·2019-08-30 12:48
閱讀 3109·2019-08-30 12:46
閱讀 2096·2019-08-30 11:16
閱讀 1489·2019-08-30 10:48
閱讀 2859·2019-08-29 16:54