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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

Generator 函数的含义与用法

發布時間:2025/3/21 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Generator 函数的含义与用法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

異步編程對 JavaScript 語言太重要。JavaScript 只有一根線程,如果沒有異步編程,根本沒法用,非卡死不可。

以前,異步編程的方法,大概有下面四種。

  • 回調函數
  • 事件監聽
  • 發布/訂閱
  • Promise 對象

ECMAScript 6 (簡稱 ES6 )作為下一代 JavaScript 語言,將 JavaScript 異步編程帶入了一個全新的階段。這組系列文章的主題,就是介紹更強大、更完善的 ES6 異步編程方法。

新方法比較抽象,初學時,我常常感到費解,直到很久以后才想通,異步編程的語法目標,就是怎樣讓它更像同步編程。這組系列文章,將幫助你深入理解 JavaScript 異步編程的本質。所有將要講到的內容,都已經實現了。也就是說,馬上就能用,套用一句廣告語,就是"未來已來"。

一、什么是異步?

所謂"異步",簡單說就是一個任務分成兩段,先執行第一段,然后轉而執行其他任務,等做好了準備,再回過頭執行第二段。比如,有一個任務是讀取文件進行處理,異步的執行過程就是下面這樣。

上圖中,任務的第一段是向操作系統發出請求,要求讀取文件。然后,程序執行其他任務,等到操作系統返回文件,再接著執行任務的第二段(處理文件)。

這種不連續的執行,就叫做異步。相應地,連續的執行,就叫做同步。

上圖就是同步的執行方式。由于是連續執行,不能插入其他任務,所以操作系統從硬盤讀取文件的這段時間,程序只能干等著。

二、回調函數的概念

JavaScript 語言對異步編程的實現,就是回調函數。所謂回調函數,就是把任務的第二段單獨寫在一個函數里面,等到重新執行這個任務的時候,就直接調用這個函數。它的英語名字 callback,直譯過來就是"重新調用"。

讀取文件進行處理,是這樣寫的。

fs.readFile('/etc/passwd', function (err, data) {if (err) throw err;console.log(data); });

上面代碼中,readFile 函數的第二個參數,就是回調函數,也就是任務的第二段。等到操作系統返回了 /etc/passwd 這個文件以后,回調函數才會執行。

一個有趣的問題是,為什么 Node.js 約定,回調函數的第一個參數,必須是錯誤對象err(如果沒有錯誤,該參數就是 null)?原因是執行分成兩段,在這兩段之間拋出的錯誤,程序無法捕捉,只能當作參數,傳入第二段。

三、Promise

回調函數本身并沒有問題,它的問題出現在多個回調函數嵌套。假定讀取A文件之后,再讀取B文件,代碼如下。

fs.readFile(fileA, function (err, data) {fs.readFile(fileB, function (err, data) {// ...}); });

不難想象,如果依次讀取多個文件,就會出現多重嵌套。代碼不是縱向發展,而是橫向發展,很快就會亂成一團,無法管理。這種情況就稱為"回調函數噩夢"(callback hell)。

Promise就是為了解決這個問題而提出的。它不是新的語法功能,而是一種新的寫法,允許將回調函數的橫向加載,改成縱向加載。采用Promise,連續讀取多個文件,寫法如下。

var readFile = require('fs-readfile-promise');readFile(fileA) .then(function(data){console.log(data.toString()); }) .then(function(){return readFile(fileB); }) .then(function(data){console.log(data.toString()); }) .catch(function(err) {console.log(err); });

上面代碼中,我使用了?fs-readfile-promise?模塊,它的作用就是返回一個 Promise 版本的 readFile 函數。Promise 提供 then 方法加載回調函數,catch方法捕捉執行過程中拋出的錯誤。

可以看到,Promise 的寫法只是回調函數的改進,使用then方法以后,異步任務的兩段執行看得更清楚了,除此以外,并無新意。

Promise 的最大問題是代碼冗余,原來的任務被Promise 包裝了一下,不管什么操作,一眼看去都是一堆 then,原來的語義變得很不清楚。

那么,有沒有更好的寫法呢?

四、協程

傳統的編程語言,早有異步編程的解決方案(其實是多任務的解決方案)。其中有一種叫做"協程"(coroutine),意思是多個線程互相協作,完成異步任務。

協程有點像函數,又有點像線程。它的運行流程大致如下。

第一步,協程A開始執行。

第二步,協程A執行到一半,進入暫停,執行權轉移到協程B。

第三步,(一段時間后)協程B交還執行權。

第四步,協程A恢復執行。

上面流程的協程A,就是異步任務,因為它分成兩段(或多段)執行。

舉例來說,讀取文件的協程寫法如下。

function asnycJob() {// ...其他代碼var f = yield readFile(fileA);// ...其他代碼 }

上面代碼的函數 asyncJob 是一個協程,它的奧妙就在其中的 yield 命令。它表示執行到此處,執行權將交給其他協程。也就是說,yield命令是異步兩個階段的分界線。

協程遇到 yield 命令就暫停,等到執行權返回,再從暫停的地方繼續往后執行。它的最大優點,就是代碼的寫法非常像同步操作,如果去除yield命令,簡直一模一樣。

五、Generator函數的概念

Generator 函數是協程在 ES6 的實現,最大特點就是可以交出函數的執行權(即暫停執行)。

function* gen(x){var y = yield x + 2;return y; }

上面代碼就是一個 Generator 函數。它不同于普通函數,是可以暫停執行的,所以函數名之前要加星號,以示區別。

整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用 yield 語句注明。Generator 函數的執行方法如下。

var g = gen(1); g.next() // { value: 3, done: false } g.next() // { value: undefined, done: true }

上面代碼中,調用 Generator 函數,會返回一個內部指針(即遍歷器?)g 。這是 Generator 函數不同于普通函數的另一個地方,即執行它不會返回結果,返回的是指針對象。調用指針 g 的 next 方法,會移動內部指針(即執行異步任務的第一段),指向第一個遇到的 yield 語句,上例是執行到 x + 2 為止。

換言之,next 方法的作用是分階段執行 Generator 函數。每次調用 next 方法,會返回一個對象,表示當前階段的信息( value 屬性和 done 屬性)。value 屬性是 yield 語句后面表達式的值,表示當前階段的值;done 屬性是一個布爾值,表示 Generator 函數是否執行完畢,即是否還有下一個階段。

六、Generator 函數的數據交換和錯誤處理

Generator 函數可以暫停執行和恢復執行,這是它能封裝異步任務的根本原因。除此之外,它還有兩個特性,使它可以作為異步編程的完整解決方案:函數體內外的數據交換和錯誤處理機制。

next 方法返回值的 value 屬性,是 Generator 函數向外輸出數據;next 方法還可以接受參數,這是向 Generator 函數體內輸入數據。

function* gen(x){var y = yield x + 2;return y; }var g = gen(1); g.next() // { value: 3, done: false } g.next(2) // { value: 2, done: true }

上面代碼中,第一個 next 方法的 value 屬性,返回表達式 x + 2 的值(3)。第二個 next 方法帶有參數2,這個參數可以傳入 Generator 函數,作為上個階段異步任務的返回結果,被函數體內的變量 y 接收。因此,這一步的 value 屬性,返回的就是2(變量 y 的值)。

Generator 函數內部還可以部署錯誤處理代碼,捕獲函數體外拋出的錯誤。

function* gen(x){try {var y = yield x + 2;} catch (e){ console.log(e);}return y; }var g = gen(1); g.next(); g.throw'出錯了'; // 出錯了

上面代碼的最后一行,Generator 函數體外,使用指針對象的 throw 方法拋出的錯誤,可以被函數體內的 try ... catch 代碼塊捕獲。這意味著,出錯的代碼與處理錯誤的代碼,實現了時間和空間上的分離,這對于異步編程無疑是很重要的。

七、Generator 函數的用法

下面看看如何使用 Generator 函數,執行一個真實的異步任務。

var fetch = require('node-fetch');function* gen(){var url = 'https://api.github.com/users/github';var result = yield fetch(url);console.log(result.bio); }

上面代碼中,Generator 函數封裝了一個異步操作,該操作先讀取一個遠程接口,然后從 JSON 格式的數據解析信息。就像前面說過的,這段代碼非常像同步操作,除了加上了 yield 命令。

執行這段代碼的方法如下。

var g = gen(); var result = g.next();result.value.then(function(data){return data.json(); }).then(function(data){g.next(data); });

上面代碼中,首先執行 Generator 函數,獲取遍歷器對象,然后使用 next 方法(第二行),執行異步任務的第一階段。由于?Fetch 模塊返回的是一個 Promise 對象,因此要用 then 方法調用下一個next 方法。

可以看到,雖然 Generator 函數將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)。本系列的后面部分,就將介紹如何自動化異步任務的流程管理。另外,本文對 Generator 函數的介紹很簡單,詳盡的教程請閱讀我寫的《ECMAScript 6入門》。

總結

以上是生活随笔為你收集整理的Generator 函数的含义与用法的全部內容,希望文章能夠幫你解決所遇到的問題。

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