日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何理解Generator

發布時間:2025/7/25 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何理解Generator 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么要用generator

在前端開發過程中我們經常需要先請求后端的數據,再用拿來的數據進行使用網頁頁面渲染等操作,然而請求數據是一個異步操作,而我們的頁面渲染又是同步操作,這里ES6中的generator就能發揮它的作用,使用它可以像寫同步代碼一樣寫異步代碼。下面是一個例子,先忽略下面的寫法,后面會詳細說明。如果你已經理解generator基礎可以直接跳過這部分和語法部分,直接看深入理解的部分。

function *foo() {// 請求數據var data = yield makeAjax('http://www.example.com');render(data); } 復制代碼

在等待數據的過程中會繼續執行其他部分的代碼,直到數據返回才會繼續執行foo中后面的代碼,這是怎么實現的那?我們都知道js是單線程的,就是說我們不可能同時執行兩段代碼,要實現這種效果,我們先來猜想下(ps:當然后面我都看過了,這里只是幫助大家理解,帶著問題去看),我們來假設有一個“王杖”(指代cpu的執行權),誰拿到這個“王杖”,誰就可以做自己想做的事,現在代碼執行到foo我們現在拿著“王杖”然后向服務器請求數據,現在數據還沒有返回,我們不能干等著。作為王我們有著高尚的馬克思主義思想,我們先把自己的權利交出去,讓下一個需要用的人先用著,當然前提是要他們約定好一會兒有需要,再把“王杖”還給我們。等數據返回之后,我們再把我們的“王杖”要回來,就可以繼續做我們想做的事情了。 如果你理解了這個過程,那么恭喜你,你已經基本理解了generator的運行機制,我這么比喻雖然有些過程不是很貼切,但基本是這么個思路。更多的東西還是向下看吧。

generator語法

generator函數

在用generator之前,我們首先要了解它的語法。在上面也看到過,它跟函數聲明很像,但后面有多了個*號,就是function *foo() { },當然也可以這么寫function* foo() { }。這里兩種寫法沒有任何區別,全看個人習慣,這篇文章里我會用第一種語法。現在我們按這種語法聲明一個generator函數,供后面使用。

function *foo() {} 復制代碼

yield

到目前為止,我們還什么也干不了,因為我們還缺少了一個重要的老伙計yield。yield翻譯成漢語是產生的意思。yield會讓我們跟在后面的表達式執行,然后交出自己的控制權,停在這里,直到我們調用next()才會繼續向下執行。這里新出現了next我們先跳過,先說說generator怎么執行。先看一個例子。

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); 復制代碼

下面我們來逐步分析,首先我們定義了一個generator函數foo,然后我們執行它foo(),這里跟普通函數不同的是,它執行完之后返回的是一個迭代器,等著我們自己卻調用一個又一個的yield。怎么調用那,這就用到我們前面提到的next了,它能夠讓迭代器一個一個的執行。好,現在我們調用第一個it.next(),函數會從頭開始執行,然后執行到了第一個yield,它首先計算了1 + 1,嗯,然后停了下來。然后我們調用第二個it.next(2),注意我這里傳入了一個2作為next函數的參數,這個2傳給了a作為它的值,你可能還有很多其他的疑問,我們詳細的后面再說。接著來,我們的it.next(2)執行到了第二個yield,并計算了2 + a由于a是2所以就變成了2 + 2。第三步我們再調用it.next(4),過程跟上一步相同,我們把b賦值為4繼續向下執行,執行到了最后打印出我們的b為4。這就是generator執行的全部的過程了。現在弄明白了yield跟next的作用,回到剛才的問題,你可能要問,為什么要在next中傳入2和4,這里是為了方便理解,我手動計算了1 + 1和2 + 2的值,那么程序自己計算的值在哪里?是next函數的返回值嗎,帶著這個疑問,我們來看下面一部分。

next

next的參數

next可以傳入一個參數,來作為上一次yield的表達式的返回值,就像我們上面說的it.next(2)會讓a等于2。當然第一次執行next也可以傳入一個參數,但由于它沒有上一次yield所以沒有任何東西能夠接受它,會被忽略掉,所以沒有什么意義。

next的返回值

在這部分我們說說next返回值,廢話不多說,我們先打印出來,看看它到底是什么,你可以自己執行一下,也可以直接看我執行的結果。

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)); 復制代碼

執行結果:

{ value: 2, done: false } { value: 4, done: false } 4 { value: undefined, done: true } 復制代碼

看到這里你會發現,yield后面的表達式執行的結果確實返回了,不過是在返回值的value字段中,那還有done字段使用來做什么用的那。其實這里的done是用來指示我們的迭代器,就是例子中的it是否執行完了,仔細觀察你會發現最后一個it.next(4)返回值是done: true的,前面的都是false,那么最后一個打印值的undefined又是什么那,因為我們后面沒有yield了,所以這里沒有被計算出值,那么怎么讓最后一個有值那,很簡單加個return。我們改寫下上面的例子。

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)); 復制代碼

執行結果:

{ value: 2, done: false } { value: 4, done: false } { value: 5, done: true } 復制代碼

最后的next的value的值就是最終return返回的值。到這里我們就不再需要手動計算我們的值了,我們在改寫下我們的例子。

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的基礎部分。但是還有更多深入的東西需要我們進一步挖掘,看下去,相信你會有收獲的。

深入理解

前兩部分我們學習了為什么要用generator以及generator的語法,這些都是基礎,下面我們來看點不一樣的東西,老規矩先帶著問題才能更有目的性的看,這里先提出幾個問題:

  • 怎樣在異步代碼中使用,上面的例子都是同步的啊
  • 如果出現錯誤要怎么進行錯誤的處理
  • 一個個調用next太麻煩了,能不能循環執行或者自動執行那

迭代器

進行下面所有的部分之前我們先說一說迭代器,看到現在,我們都知道generator函數執行完返回的是一個迭代器。在ES6中同樣提供了一種新的迭代方式for...of,for...of可以幫助我們直接迭代出每個的值,在數組中它像這樣。

for (var i of ['a', 'b', 'c']) {console.log(i); }// 輸出結果 // 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); }// 輸出結果 // 1 // 2 // 3 復制代碼

現在我們發現for...of會直接取出我們每一次計算返回的值,直到done: true。這里注意,我們的4沒有打印出來,說明for...of迭代,是不包括done為true的時候的值的。

下面我們提一個新的問題,如果在generator中執行generator會怎么樣?這里我們先認識一個新的語法yield *,這個語法可以讓我們在yield跟一個generator執行器,當yield遇到一個新的generator需要執行,它會先將這個新的generator執行完,再繼續執行我們當前的generator。這樣說可能不太好理解,我們看代碼。

function *foo() {yield 2;yield 3;yield 4; }function * bar() {yield 1;yield *foo();yield 5; }for ( var v of bar()) {console.log(v); } 復制代碼

這里有兩個generator我們在bar中執行了foo,我們使用了yield *來執行foo,這里的執行順序會是yield 1,然后遇到foo進入foo中,繼續執行foo中的yield 2直到foo執行完畢。然后繼續回到bar中執行yield 5所以最后的執行結果是:

1 2 3 4 5 復制代碼

異步請求

我們上面的例子一直都是同步的,但實際上我們的應用是在異步中,我們現在來看看異步中怎么應用。

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(); 復制代碼

這里又回到一開頭說的那個例子,異步請求在執行到yield的時候交出控制權,然后等數據回調成功后在回調中交回控制權。所以像同步一樣寫異步代碼并不是說真的變同步了,只是異步回調的過程被封裝了,從外面看不到而已。

錯誤處理

我們都知道在js中我們使用try...catch來處理錯誤,在generator中類似,如果在generator內發生錯誤,如果內部能處理,就在內部處理,不能處理就繼續向外冒泡,直到能夠處理錯誤或最后一層。

內部處理錯誤:

// 內部處理 function *foo() {try {yield Number(4).toUpperCase();} catch(e) {console.log('error in');} }var it = foo(); it.next();// 運行結果:error in 復制代碼

外部處理錯誤:

// 外部處理 function *foo() {yield Number(4).toUpperCase(); }var it = foo(); try {it.next(); } catch(e) {console.log('error out'); }// 運行結果:error out 復制代碼

在generator的錯誤處理中還有一個特殊的地方,它的迭代器有一個throw方法,能夠將錯誤丟回generator中,在它暫停的地方報錯,再往后就跟上面一樣了,如果內部能處理則內部處理,不能內部處理則繼續冒泡。

內部處理結果:

function *foo() {try {yield 1;} catch(e) {console.log('error', e);}yield 2;yield 3; }var it = foo(); it.next(); it.throw('oh no!');// 運行結果:error oh no! 復制代碼

外部處理結果:

function *foo() {yield 1;yield 2;yield 3; }var it = foo(); it.next(); try {it.throw('oh no!'); } catch (e) {console.log('error', e); }// 運行結果:error oh no! 復制代碼

根據測試,發現迭代器的throw也算作一次迭代,測試代碼如下:

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());// 運行結果 // { value: 1, done: false } // error oh no! // { value: undefined, done: true } 復制代碼

當用throw丟回錯誤的時候,除了try中的語句,迭代器迭代掉了yield 3下次再迭代就是,就是最后結束的值了。錯誤處理到這里就沒有了,就這么點東西^_^。

自動運行

generator能不能自動運行?當然能,并且有很多這樣的庫,這里我們先自己實現一個簡單的。

function run(g) {var it = g();// 利用遞歸進行迭代(function iterator(val) {var ret = it.next(val);// 如果沒有結束if(!ret.done) {// 判斷promiseif(typeof ret.value === 'object' && 'then' in ret.value) {ret.value.then(iterator);} else {iterator(ret.value);}}})(); } 復制代碼

這樣我們就能自動處理運行我們的generator了,當然我們這個很簡單,沒有任何錯誤處理,如何讓多個generator同時運行,這其中涉及到如何進行控制權的轉換問題。我寫了一個簡單的執行器Fo,其中包含了Kyle Simpson大神的一個ping-pong的例子,感興趣的可以看下這里是傳送門,當然能順手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

總結

以上是生活随笔為你收集整理的如何理解Generator的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。