javascript異步編程解決方案

*本文大部分內容來自《深入淺出node.js》
衆所周知,node與其他後臺不同的很大一部分原因在於其異步I/O,異步使得程序運行不會被阻塞,這也是node的性能所在。但是異步編程往往不是那麼友好,在此我們爲異步編程歸納了一些常用的解決方案,這些方案同樣適合在客戶端採納。
1.事件發佈/訂閱機制
2.Promise/Deferred模式
3.流程控制庫

事件發佈/訂閱模式

事件發佈/訂閱模式使用很簡單,如下所示:

emitter.on("event1",function(message){
    alert(message);
});
emitter.emit("event1","Hello Baby");

訂閱事件實際上就是一個高階函數的應用,將回調函數傳入一個第三方,然後在合適的時機取出來運行就可以了。可以理解爲將原來兩者的耦合關係解開,插入一個第三者,原來的兩者都同時與這插進來的第三個者耦合。基礎實現的代碼比較簡單,就不寫出來了。

*如果對同一個事件添加了超過10個偵聽,在node中會得到一條警告,設計者認爲可能造成內存泄漏,可以使用emitter.setMaxListeners(0)來將這個限制去掉。

對於事件發佈/訂閱模式,一個推薦模塊是EventProxy模塊,該模塊提供了all(),tail(),after(),fail(),done()等方法,使用起來十分便捷,詳細不再展開。


Promise/Deferred模式

事件發佈/訂閱模式需要預先定義後續的執行流程,而Promise/Deferred模式則是先執行異步,延遲傳遞處理的方式。

Promise/A模型中規定了一個Promise對象只要具備then()方法即可,then()方法中接收成功與失敗的回調。Promise/Deferred模式實際上也是使用了事件發佈/訂閱模式來完成的,簡單實現如下:

var Promise = function(){
    EventEmitter.call(this);
};
util.inherits(Promise,EventEmitter);

Promise.prototype.then = function(successHandler,errorHandler,progressHandler){
    if(typeof successHandler === "function"){
        this.once("success",successHandler);
    }
    if(typeof errorHandler === "function"){
        this.once("error",errorHandler);
    }
    if(typeof progressHandler === "function"){
        this.once("progress",progressHandler);
    }
    return this;
};

var Deferred = function(){
    this.state = "unSuccess";
    this.promise = new Promise();
};

Deferred.prototype.resolve = function(obj){
    this.state = "success";
    this.promise.emit("success",obj);
};
Deferred.prototype.reject = function(obj){
    this.state = "error";
    this.promise.emit("error",obj);
};
Deferred.prototype.progress = function(obj){
    this.promise.emit("progress",obj);
};

定義一個Promise對象繼承node的events模塊,在promise原型上定義了then()方法,接收三個參數,分別是成功回調,失敗回調和progress回調。在then()方法中利用once註冊了三個事件,然後返回this以便鏈式調用(關於鏈式調用詳細實現建議閱讀《深入淺出node.js》異步編程部分)。

定義了一個Deferred對象,在裏面定義了狀態,同時將Promise對象拿過來以便能用到events提供的api。定義了resolve,reject和progress方法響應在Promise中註冊的事件。至於Promise/Deferred模式的使用還是十分簡潔明瞭,就不再贅述了。


流程控制庫

最知名的流程控制模塊非async莫屬,在node開發中,流程控制是最基本的需求。async模塊提供了一系列的api來滿足這些需求,其內部實現主要是對回調函數的注入。

series()方法實現一組任務的串行執行;parallel()方法實現一組任務並行執行;waterfall方法實現了有依賴的異步串行執行;甚至提供了強大的auto方法根據依賴關係自動分析,以最佳的順序執行。

簡單解釋一下其中series()的實現:

async.series([
    function(cb){
        fs.readFile('file1','utf-8',cb);
    },
    function(cb){
        fs.readFile('file2','utf-8',cb);
    }
],function(error,results){
    //results => [file1,file2]
});

等同於:

fs.readFile('file1','utf-8',function(err,content){
    if(err){
        return cb(err);
    }
    fs.readFile('file1','utf-8',function(err, data){
            if(err){
                return cb(err);
            }
            cb(null,[content,data]);
        }
    );
});


下面那段代碼再多來幾個異步就成了“回調地獄”,而async模塊給我們封裝好了這段代碼,我們只需要愉快的寫成優雅的順序模式就好了。

*在node的異步併發中有一個問題就是當併發量過大時,下層服務器會吃不消,因爲node的I/O操作是調用子線程去實現的,並且併發量過大,操作系統的文件描述符數量會用光並拋出錯誤too many open files。在這裏一個解決方案就是使用bagpipe模塊,該模塊可以對異步API進行過載保護。


ok,異步編程解決方案就介紹到這裏,有興趣的同學還可以去看看ES7的Async/Await模塊,那也是一種很優雅的異步編程方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章