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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

深入研究ES6 Generators

發布時間:2023/12/2 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入研究ES6 Generators 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  ES6 Generators系列:

  • ES6 Generators基本概念
  • 深入研究ES6 Generators
  • ES6 Generators的異步應用
  • ES6 Generators并發
  •   如果你還不知道什么是ES6 generators,請看我的前一篇文章“ES6 Generators基本概念” 。如果你已經對它有所了解,本文將帶你深入了解ES6 generators的一些細節。

    ?

    錯誤處理

      ES6 generators設計中最牛逼的部分之一就是generator函數內部的代碼是同步的,即使在generator函數外部控制是異步進行的。

      也就是說,你可以使用任何你所熟悉的錯誤處理機制來簡單地在generator函數中處理錯誤,例如使用try..catch機制。

      來看一個例子:

    function *foo() {try {var x = yield 3;console.log( "x: " x ); // 有可能永遠也不會運行到這兒! }catch (err) {console.log( "Error: " err );} }

      盡管函數會在yield 3表達式的位置暫停任意長的時間,但是如果有錯誤被發回generator函數,try..catch依然會捕獲該錯誤!你可以嘗試在異步回調中調用上面的代碼。

      那么,如何才能將錯誤精準地發回給generator函數呢?

    var it = foo();var res = it.next(); // { value:3, done:false }// 這里我們不調用next(..)方法,而直接拋出一個異常: it.throw( "Oops!" ); // Error: Oops!

      這里我們使用了另一個方法throw(..),它會在generator函數暫停的位置拋出一個錯誤,然后try..catch語句會捕獲這個錯誤!

      注意:如果你通過throw(..)方法向generator函數拋出一個錯誤,但是該generator函數中并沒有try..catch語句來捕獲該錯誤,那么這個錯誤會被傳回來(如果這個錯誤沒有被其它代碼捕獲,則會被當作一個未處理的異常向上拋出)。所以:

    function *foo() { }var it = foo(); try {it.throw( "Oops!" ); } catch (err) {console.log( "Error: " err ); // Error: Oops! }

      顯然,反方向的錯誤處理也是可行的,看下面的代碼:

    function *foo() {var x = yield 3;var y = x.toUpperCase(); // 可能會引發類型錯誤! yield y; }var it = foo();it.next(); // { value:3, done:false }try {it.next( 42 ); // 42沒有toUpperCase()方法 } catch (err) {console.log( err ); // toUpperCase()引發TypeError錯誤 }

    ?

    Generators委托

      你可以在一個generator函數體內調用另一個generator函數,不是通過普通的方式實例化一個generator函數,實際上是將當前generator函數的迭代控制委托給另一個generator函數。我們通過關鍵字yield *來實現。看下面的代碼:

    function *foo() {yield 3;yield 4; }function *bar() {yield 1;yield 2;yield *foo(); // yield *將當前函數的迭代控制委托給另一個generator函數foo()yield 5; }for (var v of bar()) {console.log( v ); } // 1 2 3 4 5

      注意這里我們依然推薦yield *foo()這種寫法,而不用yield* foo(),我在前一篇文章中也提到過這一點(推薦使用function *foo(){}而不用function* foo(){})。事實上,在很多其它的文章和文檔中也都采用了前者,這種寫法會讓你的代碼看起來更清晰一些。

      我們來看一下上面代碼的運行原理。在for..of循環遍歷中,通過隱式調用next()方法將表達式yield 1yield 2的值返回,這一點我們在前一篇文章中已經分析過了。在關鍵字yield *的位置,程序實例化并將迭代控制委托給另一個generator函數foo()。一旦通過yield *將迭代控制從*bar()委托給*foo()(只是暫時性的),for..of循環將通過next()方法遍歷foo(),因此表達式yield 3yield 4將對應的值返回給for..of循環。當對*foo()的遍歷結束后,委托控制又重新回到之前的那個generator函數,所以表達式yield 5返回了對應的值。

      上面的代碼很簡單,只是通過yield表達式輸出值。當然,你完全可以不通過for..of循環而手動通過next(..)方法并傳入相應的值來進行遍歷,這些傳入的值也會通過yield *關鍵字傳遞給對應的yield表達式中。看下面的例子:

    function *foo() {var z = yield 3;var w = yield 4;console.log( "z: " z ", w: " w ); }function *bar() {var x = yield 1;var y = yield 2;yield *foo(); // `yield*` delegates iteration control to `foo()`var v = yield 5;console.log( "x: " x ", y: " y ", v: " v ); }var it = bar();it.next(); // { value:1, done:false } it.next( "X" ); // { value:2, done:false } it.next( "Y" ); // { value:3, done:false } it.next( "Z" ); // { value:4, done:false } it.next( "W" ); // { value:5, done:false } // z: Z, w: W it.next( "V" ); // { value:undefined, done:true } // x: X, y: Y, v: V

      雖然這里我們只展示了一級委托,但理論上可以有任意多級委托,就是說上例中的generator函數*foo()中還可以有yield *表達式,從而將控制進一步委托給另外的generator函數,一級一級傳遞下去。

      還有一點就是yield *表達式允許接收被委托的generator函數的return返回值。

    function *foo() {yield 2;yield 3;return "foo"; // 字符串"foo"會被返回給yield *表達式 }function *bar() {yield 1;var v = yield *foo();console.log( "v: " v );yield 4; }var it = bar();it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // "v: foo" { value:4, done:false } it.next(); // { value:undefined, done:true }

      看上面的代碼,通過yield *foo()表達式,程序將控制委托給generator函數*foo(),當函數foo()執行完畢后,通過return語句將值(字符串"foo")返回給yield *表達式,然后在bar()函數中,這個值最終被賦值給變量v

      Yieldyield *之間有個很有趣的區別:在yield表達式中,接收的值是由隨后的next(..)方法傳入的參數,但是在yield *表達式中,它接收的是被委托的generator函數中return語句返回的值(此時通過next(..)方法將值傳入的過程是透明的)。

      你也可以在yield *委托中進行雙向錯誤處理:

    function *foo() {try {yield 2;}catch (err) {console.log( "foo caught: " err );}yield; // 暫停// 拋出一個錯誤throw "Oops!"; }function *bar() {yield 1;try {yield *foo();}catch (err) {console.log( "bar caught: " err );} }var it = bar();it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.throw( "Uh oh!" ); // 將會被foo()中的try..catch捕獲 // foo caught: Uh oh! it.next(); // { value:undefined, done:true } --> 注意這里不會出現錯誤! // bar caught: Oops!

      在上面的代碼中,throw("Uh oh!")方法拋出一個錯誤,該錯誤被yield *委托的generator函數*foo()中的try..catch所捕獲。同樣地,* foo()中的throw "Oops!"語句將錯誤拋回給*bar(),然后被*bar()中的try..catch捕獲。如果錯誤沒有被捕獲到,則會繼續向上拋出。

    ?

    總結

      從代碼語義層面來看,generator函數是同步執行的,這意味著你可以在yield語句中使用try..catch來處理錯誤。另外,generator遍歷器還有一個throw(..)方法,可以在其暫停的地方拋出一個錯誤,這個錯誤也可以被generator函數內部的try..catch捕獲。

      關鍵字yield *允許你在當前的generator函數內部委托并遍歷另一個generator函數。我們可以將參數通過yield *傳入到被委托的generator函數體中,當然,錯誤信息也會通過yield *被傳回來。

      到目前為止我們還有一個最基本的問題沒有回答,那就是如何在異步模式中使用generator函數。前面我們看到的所有對generator函數的遍歷都是同步執行的。

      關鍵是要構造一種機制,能夠使generator函數在暫停的時候啟動一個異步任務,然后在異步任務結束時恢復generator函數的執行(通過調用next()方法)。我們將在下一篇文章中探討在generator函數中創建這種異步控制的各種方法。敬請關注!


    更多專業前端知識,請上 【猿2048】www.mk2048.com

    總結

    以上是生活随笔為你收集整理的深入研究ES6 Generators的全部內容,希望文章能夠幫你解決所遇到的問題。

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