接觸nodejs
時間不長,若是有所紕漏,請你們批評指正javascript
q
衆所周知,nodejs
是異步的,可是何爲異步呢?就是設置一個任務後當即返回,而後加上一個監聽,當任務結束的時候,就去調用監聽。html
好比下面的代碼:java
fs = require('fs') fs.readFile('/etc/hosts', 'utf8', function (err,data) { // 設置了一個監聽,文件讀取完畢之後調用 if (err) { return console.log(err); } console.log(data); }); // 不阻塞,立刻返回 console.log("starting read file..."); // result: // starting read file... // host file content
要是對於簡單的任務,這是一個很是好的設計,給你一個任務,任務作好了立刻告訴我,而後觸發相應的操做node
可是若是對於複雜的任務,就未必是一種好的作法了。好比咱們如今有多個任務須要進行處理,可是這些任務之間存在以來關係,好比任務二須要在任務一完成之後才能夠開始,任務三要在任務二後面才能夠開始。。。若是按照上面回調函數的寫法:git
task1(function (value1) { task2(value1, function(value2) { task3(value2, function(value3) { task4(value3, function(value4) { // Do something with value4 // ... more task ... // I am in the callback hell }); }); }); });
這種寫法看起來雖然簡單,可是若是在各個任務中都含有多個複雜的邏輯操做,須要串行操做的任務一旦變多,那麼這種回調的寫法就可讀性很是差,並且難以維護。也就是所謂的回調地獄。github
拿有沒有什麼方法能夠簡化這種複雜的操做呢?答案是確定的,那就是nodejs
裏面的q
模塊。express
q
模塊不只僅是爲了解決回調地獄的問題,它還能很大程度上輔助你進行一些須要並行,串行,定時等操做。npm
promise
是用來取代回調函數來進行異步操做的另外一種方案promise
咱們先來看一下大牛對promise
的定義服務器
A promise is an abstraction for asynchronous programming. It’s an object that proxies for the return value or the exception thrown by a function that has to do some asynchronous processing. — Kris Kowal on JSJ
咱們作什麼事都不要忘了最初的目的,咱們最初採用回調的目的就是佈置一件任務,任務結束之後,就將操做的數據傳入咱們註冊的函數,咱們再去處理數據。
promise
作的事情也是一樣的目的,爲了異步操做獲得數據,首先佈置一件任務,而後返回一個promise
對象,該promise
對象承諾必定給我一個結果,要是是任務成功將結果返回給我,要麼就是任務執行失敗,返回一個異常信息。
因此只要咱們有這個promise
對象,咱們就能夠在任何地方處理promise
返回給咱們的結果,就是這麼優雅。換句話說,就是將任務佈置的代碼和任務結果的處理代碼進行了分離。
咱們來看一個例子
function myReadFile(){ var deferred = Q.defer(); FS.readFile("foo.txt", "utf-8", function (error, text) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(text); } }); return deferred.promise; // 這裏返回一個承諾 } /** * many many code here */ promise.then(function(data){ console.log("get the data : "+data); },function(err){ console.err(err); });
好啦,既然知道什麼是promise了,咱們就開始探討一下q
模塊
安裝nodejs
node官網下載安裝
nodejs
新建一個工程,填寫一基本信息
mkdir qtest && cd qtest && npm init
安裝q
模塊
npm install q --save
下面是博主在學習q
模塊的時候所見所得,可能有所紕漏,若是須要q
模塊的全面資料,你們能夠參見這裏
咱們知道,當獲得一個promise
之後,咱們須要指定對應的處理函數,也就是用then
函數來指定對應的處理函數。
then
函數傳入兩個函數對象:
當promise
被fulfilled
的時候執行的函數
當promise
被rejected
的時候執行的函數
每次只有一個函數可能被執行,由於返回的promise
只可能存在一個狀態,要麼被promise被解決了,要麼promise沒有被解決。
最終then
函數返回一個新的promise
以下面所示:
var outputPromise = getInputPromise() .then(function fulfilled_function(input) {// 傳入兩個函數對象,一個用來處理fulfilled狀況,一個處理rejected狀況 }, function fulfilled_function(reason) { }); // 最終返回一個新的promise
在傳入的fulfilled_function
和rejected_function
函數中,函數的返回值會影響then
函數返回的promise
(也就是這裏的outputPromise
)的行爲:
若是返回一個普通值,promise
就是fulfilled
的
若是拋出一個異常,那麼promise
就是rejected
的
若是返回一個新的promise
,那麼then
函數返回的promise
將會被這個新的promise
取代。
若是你只關心任務的成功或者失敗狀態,上面的fulfilled_function
或者rejected_function
能夠設置爲空。
咱們來看幾個例子:
返回一個普通值:
var Q = require('q'); var outputPromise = Q(1).then(function(data){ console.log(data); return 2; // outputPromise將會fulfilled }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 FULFILLED : 2 */
拋出一個異常:
var Q = require('q'); var outputPromise = Q(1).then(function(data){ console.log(data); throw new Error("haha ,error!"); return 2; }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 REJECTED : Error: haha ,error! */
返回一個新的promise
var Q = require('q'); var outputPromise = Q(1).then(function(data){ console.log(data); return Q(3); }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 FULFILLED : 3 */
上面提到過then
函數最後會返回一個新的promise
,這樣咱們就能夠將多個promise
串聯起來,完成一系列的串行操做。
以下面所示:
return getUsername() .then(function (username) { return getUser(username); }) .then(function (user) { // if we get here without an error, // the value returned here // or the exception thrown here // resolves the promise returned // by the first line });
假如咱們如今有多個任務須要一塊兒並行操做,而後全部任務操做結束後,或者其中一個任務失敗後就直接返回,q
模塊的中的all
函數就是用來解決這個問題的。
咱們來幾個例子
成功執行:
var Q = require('q'); function createPromise(number){ return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.all(promiseArray).then(function(results){ console.log(results); }); /** 運行結果 [ 1, 4, 9, 16, 25 ] */
其中某個拋出異常:
var Q = require('q'); function createPromise(number){ if(number ===3 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.all(promiseArray).then(function(results){ console.log(results); },function (err) { console.log(err); }); /** 運行結果 [Error: haha, error!] */
可是有些時候咱們想等到全部promise
都獲得一個結果之後,咱們在對結果進行判斷,看看那些是成功的,那些是失敗的,咱們就可使用allSettled
函數。
以下所示:
var Q = require('q'); function createPromise(number){ if(number ===3 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.allSettled(promiseArray) .then(function (results) { results.forEach(function (result) { if (result.state === "fulfilled") { console.log(result.value); } else { console.error(result.reason); } }); }); /** 運行結果 1 4 [Error: haha, error!] 16 25 */
或者有時候咱們只須要其中一個promise
有結果便可,那麼any
函數就比較適合咱們
以下所示:
var Q = require('q'); function createPromise(number){ if(number ===3 || number === 1 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.any(promiseArray).then(function(first){ console.log(first); },function(error){ console.log(error); // all the promises were rejected }); /** 運行結果 4 */
上面咱們講到的都是promise
的使用,那麼如何建立一個新的promise
呢,q
模塊裏面提供好幾種方法來建立一個新的promise
:
Using Q.fcall
Using Deferreds
Using Q.promise
你可使用fcall
來直接建立一個將會fullfilled
的promise
:
return Q.fcall(function () { return 10; });
也能夠建立一個將會rejected
的promise
:
return Q.fcall(function () { throw new Error("Can't do it"); });
或者才建立一個promise
的時候傳入自定義參數:
return Q.fcall(function(number1 , number2){ return number1+number2; }, 2, 2);
上面咱們提到的fcall
方法來建立promise
的方法,雖然簡單,可是在某些時候不必定能知足咱們的需求,好比咱們如今建立一個新的promise
,須要在某個任務完成之後,讓promise
變成fulfilled
或者是rejected
狀態的,上面的fcall
方法就不適合了,由於它是直接返回的。
那麼這裏使用Deferred
來實現這種操做就再合適不過了,咱們首先建立一個promise
,而後在合適的時候將promise
設置成爲fulfilled
或者rejected
狀態的。
以下所示:
var deferred = Q.defer(); FS.readFile("foo.txt", "utf-8", function (error, text) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(text); } }); return deferred.promise;
最後一個要介紹的就是Q.Promise
函數,這個方法實際上是和Deferred
方法比較相似的,咱們要傳入一個帶有三個參數的函數對象,分別是resolve
,reject
,notify
。能夠調用這三個函數來設置promise
的狀態。
看個例子:
var Q = require('q'); var request = require('request'); function requestUrl(url) { return Q.Promise(function(resolve, reject, notify) { request(url, function (error, response, body) { if(error) reject(error); if (!error && response.statusCode == 200) { resolve(body); } }) }); } requestUrl('http://www.baidu.com').then(function(data){ console.log(data); },function(err){ console.error(err); }); /** 運行結果 百度首頁html內容 */
這裏咱們將會模擬串行和並行請求多個url
地址。
我在本地搭建了一個測試用的express
服務器:,對應代碼以下
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/address1', function(req, res, next) { res.send("This is address1"); }); router.get('/address2', function(req, res, next) { res.send("This is address2"); }); router.get('/address3', function(req, res, next) { res.send("This is address3"); }); module.exports = router;
var Q = require('q'); var request = require('request'); var urls = [ 'http://localhost:3014/q-test/address1', 'http//localhost:3014/q-test/address2', // this is wrong address 'http://localhost:3014/q-test/address3' ]; function createPromise(url){ var deferred = Q.defer(); request(url , function(err , response , body){ console.log("requested "+url); if(err) deferred.reject(err); else deferred.resolve(body); }); return deferred.promise; } var promises = urls.map(function (url) { return createPromise(url) ; }); Q.allSettled(promises) .then(function (results) { results.forEach(function (result) { if (result.state === "fulfilled") { console.log(result.value); } else { console.error(result.reason); } }); }); /** 運行結果 requested http//localhost:3014/q-test/address2 requested http://localhost:3014/q-test/address1 requested http://localhost:3014/q-test/address3 This is address1 [Error: Invalid URI "http//localhost:3014/q-test/address2"] This is address3 */
var Q = require('q'); var request = require('request'); var urls = [ 'http://localhost:3014/q-test/address1', 'http//localhost:3014/q-test/address2', // this is wrong address 'http://localhost:3014/q-test/address3', 'done' // append a useless item ]; function createPromise(url){ var deferred = Q.defer(); request(url , function(err , response , body){ if(err) deferred.reject(err); else deferred.resolve(body); }); return deferred.promise; } urls.reduce(function(soFar , url){ return soFar.then(function(data){ if(data) console.log(data); return createPromise(url); } ,function(err){ console.error(err); return createPromise(url); }) },Q(null)); /** 運行結果 requested http://localhost:3014/q-test/address1 This is address1 requested http//localhost:3014/q-test/address2 [Error: Invalid URI "http//localhost:3014/q-test/address2"] requested http://localhost:3014/q-test/address3 This is address3 requested done */
下面咱們使用q
模塊來對express
服務器進行延時操做:
var express = require('express'); var router = express.Router(); var Q = require('q'); function myDelay(ms){ // 定義延時操做,返回promise var deferred = Q.defer() ; setTimeout(deferred.resolve , ms); return deferred.promise; } /* GET home page. */ router.get('/address1', function(req, res, next) { myDelay(5000).then(function(){ res.send("This is address1"); // 時間到了就返回數據 }); }); router.get('/address2', function(req, res, next) { res.send("This is address2"); }); router.get('/address3', function(req, res, next) { res.send("This is address3"); }); module.exports = router;
好,上面的串行和並行操做咱們並無看出什麼區別,如今咱們再來跑一遍程序:
並行結果
/** 運行結果 requested http//localhost:3014/q-test/address2 requested http://localhost:3014/q-test/address3 requested http://localhost:3014/q-test/address1 This is address1 [Error: Invalid URI "http//localhost:3014/q-test/address2"] This is address3 */
串行結果
/** 運行結果 requested http://localhost:3014/q-test/address1 This is address1 requested http//localhost:3014/q-test/address2 [Error: Invalid URI "http//localhost:3014/q-test/address2"] requested http://localhost:3014/q-test/address3 This is address3 requested done */