原文 Node.js Tutorial: Promise, Generator, Event and Filestreamhtml
在前面的教程中,你已經看到了咱們在異步事件中使用回調函數。可是有些時候當它們開始不斷嵌套,而且程序變得愈來愈長愈來愈複雜的時候,回調函數就會像惡夢通常。node
在這些狀況下,Node.js 提供了額外的特性來修復咱們在使用回調的時候遇到的問題。這些特性被分紅 Promise, generates 和 events. 這篇文章咱們會更詳細地學習和理解這些概念。git
在咱們開始講 promises 以前,讓咱們首先來回顧一下 Node.js 中 "callback" 是什麼。咱們在前面的章節中看到過不少,因此讓咱們快速地過一下其中的一個。github
下面的例子展現了一段代碼片斷,它被用來鏈接一個 MongoDB 的數據庫,而且在數據庫的一條記錄中執行更新操做。數據庫
代碼解釋:npm
上面的代碼中,function(err,db) 這段代碼被稱做匿名函數的聲明或者回調函數。當 MongoClient 建立了一個 MongoDB 數據庫的鏈接時,一旦這個鏈接操做結束,它就會返回到回調函數執行。因此,某種意義上來講,鏈接操做在後臺運行,而且當它結束以後,就會調用這個回調函數。記住這是之因此 Node.js 可讓不少操做同時發生可是不會阻塞用戶執行操做的關鍵。promise
第二段代碼片斷就是回調函數被調用的時候真正執行的代碼。這個回調函數就在 MongoDB 數據庫中更新一條記錄。異步
那麼什麼是 promise 呢?promise 就是 Node.js 中回調函數的一個改進。在開發的時候,咱們可能遇到這樣的場景,咱們可能須要將多個回調函數嵌套起來。這種狀況在一段時間後會變得有些雜亂和難以維護。簡單的說,promise 就是來解決回調的這些問題的一個改進版。函數
promise 的基本語法就像下面這樣:工具
var promise = doSomethingAsync(); promise.then(onFullfilled, onRejected);
doSomethingAsync
指的是任何執行某些異步操做回調或者匿名函數
這一次,當定義回調的時候,有一個叫作promise
的值返回給咱們
當一個 promise 返回的時候,它有兩個輸出。這個由then clause
來定義。這個操做要麼成功,這裏經過onFullfilled
參數來表示,要麼出錯,這裏經過onRejected
參數來表示。
注意: 因此 promise 的關鍵是這個返回值。當咱們在 Node.js 中處理正常的回調的時候,並無返回值的概念。由於有了這個返回值,咱們對回調函數有了更多的控制權。
如今讓咱們看一個例子,看看咱們在 Node.js 應用中是如何使用 promises
的。爲了可以在 Node.js 的應用程序中使用 promise
,這個 promise
模塊首先須要下載和安裝。
接下來咱們用 promises 來修改咱們的代碼,來更新 Employee
集合中的 Employeename。
安裝 npm 模塊
爲了可以在 NodeJS 應用中使用 promise, 咱們須要安裝這個 promise 模塊,運行下面的命令
npm install promise
修改代碼來引入 promise
代碼解釋:
第一部分是包含這個promise
模塊,讓咱們可以在代碼中可使用 promise 的功能。
如今咱們能夠在 MongoClient.connect
函數後面添加then
函數啦。因此,這段代碼的意思是當創建數據庫鏈接以後,咱們須要執行定義在then
裏面的代碼片斷。
最後,咱們定義了執行更新操做的代碼片斷
注意:
若是你如今檢查 MongoDB 數據庫,你會發現若是Martin
僱員的名字存在,它會被更新成Mohan
.
若是要檢查是否在數據庫中正確插入,你須要執行下面 MongoDB 的命令。
use EmployeeDB
db.Employee.find({EmployeeName:mohan})
第一個命令是保證你連上 EmployeeDB
的數據庫。第二個命令是查找名字爲 Mohan
的僱員的紀錄
當咱們定義 promises 的時候,須要注意的是then
方法自己返回的也是一個 promise。因此,某種意義上來講 promises 能夠嵌套或者鏈式調用。
下面的例子中,咱們使用鏈式調用來定義了兩個回調函數。兩個都向數據庫插入了一條記錄。
注意:鏈式調用是把一個方法的執行連接到另外一個上的概念。假如你的應用有兩個方法分別叫作methodA
和methodB
.而且邏輯是methodB
要在methodA
以後調用,這個時候你就能夠把這種執行順序經過在methodA
以後直接調用methodB
來連接起來。
這個例子須要注意的關鍵是經過使用嵌套 promises 使得代碼變得更簡潔,可讀和可維護了。
代碼解釋:
咱們定義了兩個then
語句來實如今一個執行完以後再執行另外一個。第一個then
語句,咱們將包含數據庫鏈接的db
做爲參數傳遞給它。接下來咱們經過使用db
鏈接的屬性collection
來插入一條記錄。這個insertOne
方法被用來插入一個真正的文檔對象給Employee
集合。
接下來咱們使用第二個then
語句來向數據庫插入另外一條記錄。
若是你如今查看 MongoDB 數據庫,你會在數據庫中找到這兩條記錄。
Bluebird 是一個功能完善的 promise 庫。Bluebird 最重要的特性是它可以 promisify
其餘Node
模塊來異步地使用它們。Promisify 是一個應用在回調函數上的概念。這個概念用來保證每個回調函數會返回一些值。
因此,若是一個 NodeJS 模塊包含了一個不返回任何值的回調函數,若是咱們 Promisify
這個node
模塊,那麼全部在這個模塊內部的函數都會被自動修改保證它有返回值。
因此,你可使用 Bluebird 讓 MongoDB 模塊異步運行。這樣就從另外一層面上提升了寫 Node.js 應用程序的溫馨性。
咱們接下來就看一個若是使用 bluebird 模塊的例子。
咱們的例子首先創建一個到 EmployeeDB
數據庫中 Employee collection
的一個鏈接。若是接下來鏈接創建好了,它就會獲取全部的集合中的記錄而且在控制檯中打印出來。
安裝 npm 模塊
要在 Node 應用程序中使用 Bluebird,必須包含這個模塊,運行下面的命令:
npm install bluebird
接下來就是包含這個 bluebird 模塊,而後 promise 化整個 MongoDB 模塊。這裏的 promise 化,咱們的意思是 bluebird 確保每個定義在 MongoDB 類庫中的方法都會返回一個 promise。
代碼解釋:
require
命令是用來包含這個 Bluebird 庫的。
經過使用 Bluebird 的 promisifyAll()
這個方法來建立 MongoDB 模塊中每個方法的異步版本。這確保每個 MongoDB 模塊的方法都會在後臺中運行,而且確保 MongoDB 庫的每個方法都會返回一個 promise。
最後一步就是鏈接數據庫,獲取集合中的全部記錄而後在控制檯展現它們。
代碼解釋:
你會發現咱們這裏使用connectAsync
方法而不是正常的connect
方法來鏈接數據庫。Bluebird 是經過給每個方法添加 Async 關鍵字來區分返回 promise 和不返回 promise 的調用。因此咱們並不能保證沒有 Async 關鍵字的方法會有返回值。
和connectAsync
相似,咱們如今使用findAsync
方法來返回全部在 MongoDB 數據庫中 Employee
集合中的記錄。
最後,若是這個 findAsync
方法成功返回一個 promise ,咱們接下來就能夠定義一段代碼來遍歷集合中的每個記錄而後把它們的內容打印出來。
若是上面的步驟正確的執行,全部在Employee
集合的文檔都會被打印到控制檯,就像下面這樣.
一個自定義promise能夠經過一個叫作q
的 node 模塊來建立。這個q
模塊須要經過 node 包管理工具來下載和安裝。在使用q
這個庫以後,這個denodeify
方法被調用以後會讓任何函數變成一個會返回 promise 的函數。
下面的例子中,咱們首先建立一個簡單的函數叫作Add
,它會把兩個數加起來。咱們要讓這個函數轉化爲一個返回 promise 對象的函數.
一旦轉化成功以後,咱們就會經過這個Add
函數返回的 promise 來在控制檯中展現信息
接下來讓咱們按照下面的方法來建立一個自定義的返回 promise 對象的方法。
安裝 npm 模塊
要使用q
這個模塊,咱們須要在 NodeJs 應用程序中包含它。安裝這個q
模塊,咱們能夠運行下面的命令
npm install q
定義下面的代碼來建立自定義的 promise
代碼解釋:
第一段的做用是經過require
關鍵字包含這個q
庫,經過使用這個庫,咱們就可以定義任何函數來返回一個回調函數。
咱們建立了一個叫作 Add 的函數,它會把兩個有變量a
和b
表示的數字加起來,結果保存在c
中。
咱們使用q
庫來denodeify
(用來將任何函數轉化成可以返回 promise 的函數的方法)咱們的Add
函數。
如今咱們調用這個Add
函數就可以返回一個 promise ,由於咱們以前denodeify
了這個Add
函數
這裏的then
關鍵詞被用來表示當函數成功執行以後在控制檯中展現Addition function completed
.
當上面的代碼執行以後,就會在控制檯輸出下面的內容
generators 最近在 Node.js 中變得至關有名,這也許和它們可以作的事情有關。
Generators 是一個可以讓函數執行掛起,而且在後面的某個時間點恢復
Generators 在實現好比說懶執行
的概念的時候很是有用。這基本意味着經過掛起操做和爲所欲爲地恢復執行,咱們可以只有在咱們須要的時候取數據。
Generators 有下面兩個關鍵字:
Yield 方法:這個yield
方法是在要在中止函數執行的時候調用的,函數就在yield
方法被調用的那一行中止。
Next 方法:這個next
方法從主應用調用去恢復擁有yield
方法的函數的執行。這個函數會一直執行到遇到下一個yield
方法或者直到函數的結束.
讓咱們看一下下面的例子看看 generators 是怎麼使用的。
下面的例子中,咱們會有一個把兩個數字相加的Add
函數,可是可是咱們會在函數中不一樣的地方中止函數的執行來展現 generators 是如何使用的。
代碼解釋:
第一步是定義咱們的 generator 函數。注意這裏是經過在 function 關鍵字後面添加*
完成的。接下來咱們定義一個叫作Add
的函數,接受一個x
做爲參數.
這個yield
關鍵字只用在 generators 中。這讓它擁有可以在函數的人和位置暫停的能力。因此在這裏,這個函數會一直中止,知道咱們調用next()
函數,在第4步會調用。在這裏,x
的值會變成6而且函數的執行會中止。
這裏就是咱們調用 generator 函數而且把 5 做爲參數傳給這個函數的地方。
一旦咱們調用next()
方法,Add()
函數會恢復執行。當咱們執行到下面這一個語句var y = yield(null)
的時候又會中止執行。(此時返回的 value 是 6,而不是上面說的 null)
再次調用next()
方法,一隻執行到return x + y;
結束。(此時返回的 value 是 null,而不是上面說的 11)。
再次調用next()
方法,會直接返回 11.
Generators 被用來解決一個叫作回調hell的問題。有些時候在 Node.js 應用中,回調函數嵌套的如此之深以致於它變得太複雜而不能使用回調。
這個時候 Generators 就派上用場啦。一個最經常使用的例子就是建立定時器函數。
讓咱們看看下面的例子來看看 generators 是如何證實它比回調更有用的。
咱們的例子只建立一個簡單的時間延遲函數。咱們接下來會在 1s,2s,3s以後調用這個函數。
1, 建立一個回調函數有必要的時間延遲的代碼
代碼解釋:
這裏咱們建立了一個叫作Timedelay
的函數,裏面包含一個叫作 ptime 的參數,這個可讓咱們等待給定的時間來執行回調
下一步就是建立一條消息,告訴用戶說這個應用程序會暫停這麼多時間
2, 如今咱們來看一下若是咱們包含不少回調的代碼。假如咱們想要在每次調用以後都增長 1s 再調用回調函數,下面的代碼就是咱們須要的用來實現的回調代碼。
代碼解釋:
咱們以 1000 做爲參數調用 Timedelay 函數
接下來,咱們以 2000 做爲參數調用 Timedelay 函數
最後,咱們以 3000 做爲參數調用 Timedelay 函數
3, 如今咱們來看看如何經過 generators 來實現上面同樣的功能。