深刻淺出node(4) 異步編程

一)函數式編程基礎javascript

二)異步編程的優點和難點前端

  2.1 優點java

  2.2 難點git

    2.2.1 異常處理github

    2.2.2 函數嵌套過深web

    2.2.3 阻塞數據庫

    2.2.4 多線程編程編程

    2.2.5 異步轉同步promise

三)異步編程解決方案多線程

  3.1 事件發佈/訂閱模式

    3.1.1 解決雪崩問題

    3.1.2 多異步之間的協做方案

  3.2 Promise/Deferred模式

    3.2.1 Promises/A簡介

    3.2.2 支持序列化的Promise

 

一)函數式編程基礎

  1.1 高階函數  高階函數式能夠接受函數做爲參數或者返回值的函數,這樣在編寫程序的時候就增長了靈活性,還能造成一種後續傳遞風格的結果接受方式,將業務從返回值轉移到回到函數中

function test(x) {
    return x + 1;
}
function test2(x) {
    return x + 2;
}
function func(x,bar) {
    return bar(x);
}
console.log(func(2,test)); 3
console.log(func(2,test2)); 4

  1.2 偏函數 偏函數指建立一個調用另一個部分(參數或者變量已經預置的函數)的函數

var toString = Object.prototype.toString;
var isType = function(type) {
    return function(obj) {
        return toString.call(obj) == '[object ' + type + ']'
    }
}  //這裏咱們經過預置了type參數 來實現了偏函數

二)異步編程的優點和難點

  2.1 優點  Node中異步編程的最大優點是基於時間驅動的非阻塞的I/O模型,這樣就能使CPU和I/O互相不依賴,更好的利用資源,從而達到並行的目的(主要是減小對CPU的佔用)

  2.2  難點

    2.2.1 異常處理  在實現異步I/O的過程當中主要包含兩個階段 提交請求和處理結果 咱們在第一個階段提交請求後當即返回,錯誤有可能出如今處理結果這個階段,因此當咱們對第一個階段進行異常處理的時候發揮不了做用 Node的解決方案是將異常做爲回調函數的第一個實參傳回,若是爲空就表明異步調用沒有異常,在編寫異步方法的時候 要遵照如下的兩個原則

  1. 必須執行調用者傳入的回調函數
  2. 正確傳遞迴異常供調用者判斷
var async = function(callback) {
    //一些操做  獲取一些數據或者數據處理等
    var results = something;
    if(error) {
        return callback(error);
    }
    callback(null,results);
}

    2.2.2 函數嵌套過深  在咱們獲取資源的時候,資源之間是沒有依賴關係的,在進行結果處理的時候卻須要三者,這樣就會形成函數的嵌套

    2.2.3 阻塞代碼 對於javascript編程來講,是不存在阻塞代碼的,Node中單線程的緣由,線程須要運行事件循環的調度,長時間的佔用主線程會破壞事件循環,解決方案是統一業務邏輯,使用setTimeout()來完成相似的效果

    2.2.4 多線程編程  相似於前端提出的web workers,Node中提出了child_process,cluster是更深層的多線程方式

    2.2.5 異步轉同步  Node中同步的API較少,對於異步的調用,經過良好的流程控制,仍是可以將流程梳理成順序式的

三)異步編程解決方案 如今主要有三種典型的異步編程解決方案

  • 事件發佈/訂閱模式
  • Promise/Deferred模式
  • 流程控制庫(流程控制庫這部分介紹性的東西偏多 我沒有整理)

  3.1 事件發佈/訂閱模式 發佈/訂閱模式被普遍的應用於異步編程,它經過將回調函數事件化的執行,這樣就使得事件與具體的邏輯解耦和關聯.在進行組件的設計的時候,經過事件的方式將自定義的部分經過事件的方式暴露給外部.而且事件模式也提供了鉤子機制,利用鉤子咱們能導出內部數據或狀態給外部的調用者

  • Node中當設置過多的監聽器的時候,會收到一條警告.設計者認爲這樣會形成內存的泄漏,同時一系列的監聽器的執行有可能過多的佔用CPU,致使其餘的異步調用沒法執行
  • 必須對異常事件作好處理,不然會引起主線程的退出

能夠經過繼承events模塊來實現發佈/訂閱模式來解決業務中的問題

var events = require("events");
var util = require("util");
function Stream() {
    events.EventEmitter.call(this);
}
util.inherits(Stream,events.EventEmitter); 

   3.1.1 解決雪崩問題 雪崩問題是忽然間大量相同的操做進行訪問的時候,致使同時進行相同的查詢或者操做,數據庫沒法同時承受如此大的查詢請求,進而影響網站的整體速度

一種比較簡單的方案就是使用狀態鎖

var status = "ready";
var select = function(callback){
    if(status === "ready") {
        status = "pending";
        db.select("SQL",function(err,results){
            status = "ready";
            callback(results);
        });
    }
} // 這樣當同時進行屢次調用的時候,只有首次的調用是有數據服務的 這樣的方式並不合理

結合狀態鎖和發佈/訂閱模式來解決雪崩問題

var events = require("events");
var proxy = new events.EventEmitter();
var status = "ready";
var select = function(callback){
    proxy.once("selected",callback);
    if(status === "ready") {
        status = "pending";
        db.select("SQL",function(error,results){
            proxy.emit("selected",results);
            status = "ready";
        });
    }
}//在這種模式下一樣的請求以後執行一次,後續的請求會被加入到事件的隊列中,當數據可用時,每一個請求的回調函數都會被執行一次

  3.1.2 多異步之間的協做方案  可使用發佈/訂閱模式來解決嵌套過深和梳理業務邏輯   哨兵變量指用於檢測次數的變量 咱們可使用偏函數和哨兵變量的模式來梳理異步做業中多對一的場景

var after = function(times,callback){
    var counts = 0,results = {};
    return function(key,value){
        results[key] = value;
        count++;
        if(count === times) {
            callback(results);
        }
    }
}  //這樣在完成times調用的時候,纔會調用回調函數 而且會把以前的結果傳入   還有樸大寫的EventProxy模塊也是經過補充發布訂閱模式,來協同多異步操做

  3.2 Promise/Deferred模式  Promise/Deferred模式它是一種先執行異步延時傳遞處理的模式,它能彌補發佈/訂閱模式的不足(必須預先設定好分支),Promise/Deferred模式同時能必定上解決函數調用嵌套的問題,方便咱們更好的理解業務邏輯  在ES6中已經提供了對Promise的支持  簡單的理解Promise/Deferred模式 then()方法就是把回調函數存放起來 而後經過Deferred(延時)對象在適當的時候去調用保存起來的方法

  3.2.1 Promises/A簡介  Promises/A對單個異步操做進行了抽象的定義

  • Promise操做只會存在三種狀態:未完成態 完成態和失敗態
  • Promise狀態只能從未完成狀態往失敗狀態或者完成態轉變 而且不能逆轉 完成態和失敗態不能互相轉化(轉化後的狀態沒法更改)

在使用Promise的時候,只須要在Promise的then()方法中傳遞相應的回調函數便可,它就會在異步操做處於完成或者失敗狀態的時候調用對應的函數而且將結果做爲參數傳遞進去,then()方法只接受function對象而且執行完then()方法後繼續返回Promise()對象來實現鏈式調用

var util = require("util");
var events = require("events");
var Promise = function() {
    events.EventEmitter.call(this);
}
util.inherits(Promise,event.EventEmitter);
Promise.prototype.then = function(fulfilledHandler,errorHandler,progressHandler){
    if(typeof fulfilledHandler === 'function') {
        this.once('success',fulfilledHandler);
    }
    if(typeof errorHandler === 'function') {
        this.once('error',errorHandler);
    }
    if(typeof progressHandler === 'function') {
        this.once('progress',progressHandler);
    }
    return this;
};
var Deferred = function(){
    this.state = 'unfulfilled';
    this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj) {
    this.state = 'fulfilled';
    this.promise.emit('success',obj);
}
Deferred.prototype.reject = function(obj) {
    this.state = 'failed';
    this.promise.emit('failed',obj);
}
Deferred.prototype.progress = function(data) {
    this.promise.emit('progress',data);
} //咱們在實際操做的時候,就是經過對Deferred進行封裝來實現Promise\Deferred模式  也就是在異步操做成功的時候調用Deferred對象的resolve方法,在失敗的時候調用reject方法
下面咱們封裝一個基本的數據請求
var promisify = function(res) {
var deferred = new Deferred();
var results = '';
res.on('data',function(chunk){
results += chunk;
deferred.progress(chunk);
})
res.on('end',function(){
deferred.resolve(results);
})
res.on('error',function(err){
deferred.reject(err);
})
return deferred.promise;
}

推薦這本 promise迷你書 

  3.2.2  支持序列化的Promise

var Promise = function(){
    this.isPromise = true;
    this.queue = [];
}
Promise.prototype.then = function(fulfilledHandler,errorHandler,progressHandler){
    var handler = {};
    if(typeof fulfilledHandler === 'function') {
        handler.fulfilled = fulfilledHandler;
    }
    if(typeof errorHandler === 'function') {
        handler.error = errorHandler;
    }
    this.queue.push(handler);
    return this;
}
var Deferred = function() {
    this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj) {
    var promise = this.promise;
    var handler;
    while((handler = promise.queue.shift())) {
        if(handler && handler.fulfilled) {
            var ret = handler.fulfilled(obj);
            if(ret && ret.isPromise) {
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
}
Deferred.prototype.reject = function(err) {
    var promise = this.promise;
    var hanlder;
    while((hanlder = promise.queue.shift())) {
        if(handler && hanlder.error) {
            var ret = hanlder.error(err);
            if(ret && ret.isPromise) {
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
}//上面的的代碼進全部的回調函數保存爲對象存儲在隊列中.promise完成是依次的執行回調,若是返回新的promise對象,就將當前deferred對象的promise對象的promise設置爲新的promise對象(這個就是解決回調地獄的解決方案)
相關文章
相關標籤/搜索