nodejs q模塊

接觸nodejs時間不長,若是有所紕漏,請你們批評指正javascript

nodejs module 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模塊

q模塊不只僅是爲了解決回調地獄的問題,它還能很大程度上輔助你進行一些須要並行,串行,定時等操做。npm

promise

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模塊

q模塊的安裝

安裝nodejs

node官網下載安裝nodejs

新建一個工程,填寫一基本信息

mkdir qtest && cd qtest && npm init

安裝q模塊

npm install q --save

promise的使用

下面是博主在學習q模塊的時候所見所得,可能有所紕漏,若是須要q模塊的全面資料,你們能夠參見這裏

then 函數

咱們知道,當獲得一個promise之後,咱們須要指定對應的處理函數,也就是用then函數來指定對應的處理函數。

then函數傳入兩個函數對象:

  • promisefulfilled的時候執行的函數

  • promiserejected的時候執行的函數

每次只有一個函數可能被執行,由於返回的promise只可能存在一個狀態,要麼被promise被解決了,要麼promise沒有被解決。

最終then函數返回一個新的promise

以下面所示:

var outputPromise = getInputPromise() 
.then(function fulfilled_function(input) {// 傳入兩個函數對象,一個用來處理fulfilled狀況,一個處理rejected狀況
}, function fulfilled_function(reason) {
}); // 最終返回一個新的promise

在傳入的fulfilled_functionrejected_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的使用,那麼如何建立一個新的promise呢,q模塊裏面提供好幾種方法來建立一個新的promise

  • Using Q.fcall

  • Using Deferreds

  • Using Q.promise

Using Q.fcall

你可使用fcall來直接建立一個將會fullfilledpromise

return Q.fcall(function () {
    return 10;
});

也能夠建立一個將會rejectedpromise

return Q.fcall(function () {
    throw new Error("Can't do it");
});

或者才建立一個promise的時候傳入自定義參數:

return Q.fcall(function(number1 , number2){
    return number1+number2;
}, 2, 2);

Using Deferreds

上面咱們提到的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;

Using Q.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
*/
相關文章
相關標籤/搜索