nodejs與Promise的思想碰撞

玩node的同志們都知道,當這門語言被提出來的時候,做爲本身最爲驕傲的異步機制,卻被PHP和Python等戰團噴得不成樣子的是,他們嘲笑着nodejs那蠢蠢的無限嵌套,nodejs戰團只能以咱們只要性能!!!來安慰本身。javascript

衆所周知,javascript做爲一個單線程語言,全部工做都是阻塞的,有好多人不理解爲何說是javascript是阻塞的,怎麼能夠作到異步機制呢?前端

舉一個栗子

在咱們平時能夠接觸到的狀況下,咱們能夠用瀏覽器來觸發XMLHttpRequest(Ajax)來異步獲取數據,setTimeout、setInterval來完成定時任務,而這並非javascript的語言來決定這些異步操做的,而是解釋Javascript的瀏覽器來去操做線程做多線程操做的,能夠把這些方法理解爲瀏覽器拋出的多線程API。而nodejs是基於高性能v8來實現,它也是像瀏覽器同樣,拋出了不少操做線程的API,從而來實現異步機制java

異步的機制可讓咱們更爲節省系統資源,並不須要爲每個請求去像PHP,Tomcat同樣新開一個線程,node內部會有處理各類任務的線程(使用Net,File System,Timers 等不少模塊來操做不一樣的線程),把不一樣的異步任務分發給各個任務線程,並會彈性地爲線程分配硬件,這都是來自v8的高性能,也是爲何nodejs能面對高I/O狀況的根本緣由。node

現實


到頭來咱們必須面對血淋淋的現實,當我初接觸node的時候,代碼也是這樣寫的npm

fs.readFile(MrFileFirst,"utf8",function(err,data1){
    if(err){
        //do err thing
    }else{
        fs.readFile(MrFileSecond,"utf8",function(err,data2){
            if(err){
                //do err thing
            }else{
                mongo.find(SomeQuery,function(err,data3){
                    if(err){
                        //do err thing
                    }else{
                        //do the real thing with [data1,data2,data3]
                    }
                })
            }
        })
    }
})

Oh,my god!好好的異步機制仍是玩成了同步……並且慘不忍睹!僅僅只是想返回最後的三個數據,可是這個例子三個任務之間並沒有關係嵌套,這樣子強行把異步玩成同步的話,仍是阻塞的代碼,這段代碼的工做時序大概在這樣的:編程

和不用node並無什麼區別,徹底是阻塞的。在平時咱們能夠碰到更多的關係層級的嵌套(下一步的操做要基於上一步的結果),這時才必須使用同步去完成任務,可是要是像上面這樣寫的話,我相信你會寫到吐血的(我已經忘了我在代碼中寫過多個少if (err) {}了,由於node的底層API異步方法都是以err爲第一個參數,使得上層全部異步方法都爲這種模式)promise

進化


有人看不下去了,便自會有人站出來,咱們漸漸地實現了從無到有的過程,我最開始接觸的是阿里的瀏覽器

eventproxy

var ep = require("eventproxy");

ep.create("task1","task2","task3",function(result1,result2,result3){
    //do the real thing with [result1,result2,result3]
}).fail(function(e){
    //do err thing
});

fs.readFile(MrFileFirst,"utf8",ep.done("task1"));
fs.readFile(MrFileSecond,"utf8",ep.done("task2"));
fs.readFile(MrFileThird,"utf8",ep.done("task3"));

這樣,就能夠實現三個文件異步進行讀取,而且在三個任務都完成時進行最終的工做,時序圖以下圖:多線程

三個任務幾乎同時觸發(除去代碼的觸發時間),因此左邊的三個點其實能夠看做是一個點,而這三個任務都去同時異步進行,在三個任務都完成的時候,來觸發最後的任務。閉包

這纔是node發揮出本身優勢的地方,處理時間節省了不少(若是三個任務的時間消耗都爲1,則時間縮減了2/3),這纔是大node.js

eventproxy也有更多的用法,能夠去其npm上看看。

async

async是國外強大的異步模塊,它的功能與eventproxy類似,可是維護速度與週期特別快,畢竟是用的人多呀,可是支持國產——是一種情懷,附介紹使用async的文章
http://blog.fens.me/nodejs-async/

再進化


人老是不滿足的,而恰好是這個不滿足,才讓咱們不停地去探索想要的、更爲方便的東西。而這時,便有人想讓本身寫的代碼複用性更高,同時也不想去寫那麼多的callback去嵌套,這時便有了Promiss/A+規範,其是:

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
一個健全的通用JavaScript Promise開放標準,源於開發者,並歸於開發者

在ES6中也新增了原生Promise的使用,而以前Promise庫有promise,Q,bluebird等,在這些庫中如今已經慢慢對ES6的原生Promise做了兼容,雖然ES6如今尚未大規模投入使用過程當中。

在其中最爲出名的則是bluebird和Q庫,我使用的是bluebird,先貼一段bluebird的使用代碼感覺感覺

bluebird

//CAST
//MrFileOne.txt
//MrFileTow.txt
//MrFileThree.txt


//關係嵌套任務
var Promise = require("bluebird"),
    readFileAsync = Promise.promisify(require("fs").readFile);

readFileAsync("MrFileOne.txt","utf8")
    .then(function(data){
        //if the data contains the path of MrFileTow
        var path = ..... //do something with data
        return readFileAsync(path,"utf8");
    })
    .then(function(data){
        //if the data contains the path of MrFileThree
        var path = ..... //do something with data
        return readFileAsync(path,"utf8");
    })
    .then(function(data){
        //get the data of MrFileThree
        //do something
    })
    .catch(function(err){
        console.log(err);
    });


//無關係彙總任務
Promise.all([
        readFileAsync("MrFileOne.txt","utf8"),
        readFileAsync("MrFileTwo.txt","utf8"),
        readFileAsync("MrFileThree.txt","utf8")
    ])
    .then(function(datas){
        //do something with three data form our actors
    })
    .catch(function(err){
        console.log(err);
    });

有沒有一下被這種寫法所吸引,這就是Promise模塊的魅力,它很優雅地將函數的回調寫在了then裏面,併爲then返回一個新的Promise,以供下一次的then去回調本次返回的結果。

How

首先使用了方法:

readFileAsync = Promise.promisify(rquire("fs").readFile);

這個方法則是爲複製了readFile方法併爲其增添了Promise機制,而Promise機制是什麼呢?那就是爲其添加Promise方法和屬性後,讓整個方法的返回值爲一個Promise對象,咱們能夠經過Promise來調用then方法,來去對這個Promise方法的回調進行處理。在Promise中都默認的將第一個參數err放在了後面的catch中,使得咱們不再用寫那麼多的if(err)了。咱們能夠直接經過在then方法中經過函數參數來獲取這個Promise的異步數據,從而進行下一步的處理。

而在then方法後,其返回的也是一個Promise對象,咱們能夠在其後再次進行then來獲取上一個then的數據並進行處理。固然,咱們也能夠人爲地去決定這個then的返回參數,可是整個then方法返回的都是一個Promise對象。

readFileAsync("MrFileOne.txt","utf8")
    .then(function(data){
        if(.....){  //data isn't what we want
            Promise.reject("It's not correct data!");
        }else{
            return data;
        }
    })
    .then(function(){
        console.log("yeah! we got data!");
    })
    .catch(function(err){
        console.log(err);
    })

在上面代碼中,若是獲取到的data並非咱們想要的,則咱們可直接調用Promise.reject拋出一個ERROR,並直接交給catch來處理錯誤,因此在控制檯咱們能獲得的是「It's not correct data!」,並不會獲得「yeah! we got data!」,由於拋出錯誤後其以後的then方法並不會跟着執行。

More

固然咱們也能夠自定義多個catch來捕獲不一樣的ERROR,對其做不一樣的處理,就像下面的同樣

var customError = new Error(SOMENUMBER,SOMEDESCRIPTION)

readFileAsync("MrFileOne.txt","utf8")
    .then(function(data){
        switch(data){
            case CASE1:
                Promise.reject(customError);
            case CASE2:
                Promise.reject(new SyntaxError("noooooo!"));
        }
    })
    .catch(customError,function(err){
        //do with customError
    })
    .catch(SyntaxError,function(err){
        //do with SyntaxError
    })
    .catch(function(err){
        console.log(err);
    })

而更多的使用方法,能夠在bluebird on npm裏學習獲得,相信你看了以後會愛上Promise的。

Q

Q模塊也是一個很是優秀的Promise,它的實現原理和bluebird都大同小異,都是基於Promise/A+標準來擴展的,因此使用上甚至都是差不了多少的,選擇哪個就看我的愛好了。

Promise編程思想


重點來啦,咱們先來看一段普通的代碼

var obj = (function(){
    var variable;
    
    return {
        get: function(){
            return variable;
        },
        set: function(v){
            variable = v;
        }
    }
})();

exports.get = obj.get;
exports.set = obj.set;

這個代碼實現的是建立了一個閉包來儲存變量,那麼我在外部調用這個模塊時,則能夠去操做這個值,即實現了一個Scope變量,並把它封裝了起來。

矛盾

根據咱們之前的思想,這段代碼看起來很正常,可是這時侯我要加一判斷進去,即在get方法調用時,若是varibaleundefined,那麼我則去作一個讀文件的操做,從文件中將它讀出來,並反回,你會怎麼實現呢?

你會發現,經過以往的思惟,你是沒法作到這一方法的,那麼使用異步思惟去想一想呢,好像有點門頭:

get: function(callback){
    if(varibale){
        callback(varibale);
    }else{
        fs.readFile("SomeFile","utf8",function(err,data){
            if(err){
                //do with err
                return;
            }
            
            callback(data);
        })
    }
}

這樣……嗯咳咳,看起來彷佛好像也許解決的還能夠,可是你本身也會以爲,這其實糟透了,咱們將本來的簡單get函數更改得這麼複雜。那麼問題來了,誰會在使用的時候會想到這個get方法實際上是一個回調的方法呢?你平時使用get時你會考慮說是這個裏面有能夠是回調嗎?咱們都是直接get()來獲取它的返回值。

這就是咱們本身給本身形成的矛盾和麻煩,這也是我之前曾經遇到的。

突破

那麼在模塊化的node裏,咱們怎麼去實現這些沒必要要的麻煩呢?那就是用Promise思想去編寫本身的代碼,咱們先試着用上面說到的bluebird來加工一下這段代碼:

var Promise = require("bluebird"),
    fs = require("fs");

var obj = (function(){
    var variable;
    
    return {
        get: function(){
            if(variable){
                return Promise.resolve(variable);
            }
            
            return Promise.promisify(fs.readFile)("SomeFile","utf8");
        },
        set: function(v){
            return Promise.resolve(variable = v);
        }
    }
});

exports.get = obj.get;
exports.set = obj.set;

就是這麼漂亮,使用Promise.resolve方法則是將變量轉化爲一個Promise對象,則是咱們在外部對這個模塊進行使用時,則要求咱們使用Promise的思想去應用模塊拋出的接口,好比:

var module = require("thisModule.js");

module.get()
    .then(function(data){
        console.log(data);
                module.set("new String");
                return module.get;
    })
    .then(function(data){
        console.log(data);
    });

當咱們使用Promise思想去面對每個接口的時候,咱們能夠徹底不用考慮這個模塊的代碼是怎麼寫的,這個方法該怎麼用纔是對的,究竟是回調仍是賦值。咱們能夠很直接的在其模塊方法後then來解決一切問題!不用關心前面的工做到底作了什麼,怎麼作的,究竟是異步仍是同步,只要咱們將整個工做流程都使用Promise來作的話,那會輕鬆不少,並且代碼的可讀性會變得更好!

尼瑪!簡直是神器啊!

使用Promise編程思想去和node玩耍,你會相信真愛就在眼前。同時我也相信在前端模塊化加速的今天,Promise編程思想一定會滲透至前端的更多角落。

Finish.

相關文章
相關標籤/搜索