深刻淺出Node.js學習筆記(四)

異步編程

Node是首個將異步大規模帶到應用層面的平臺,它從內在運行機制到API的設計,無不透露出異步的氣息來。數據庫

1.函數式編程

在JavaScript中,函數做爲一等公民,使用上很是自由,不管調用它,或者做爲參數,或者做爲返回值都可。編程

函數式編程是JavaScript異步編程的基礎。promise

1.1高階函數

高階函數把函數做爲參數,或是將函數做爲返回值的函數。緩存

function Night(x){
	return function (){
		return x;
	}
}
複製代碼

事件的處理方式正是基於高階函數的特性的靈活性來完成的。bash

ECMAScript5提供的高階函數:網絡

  1. forEach();
  2. map();
  3. reduce();
  4. reduceRight();
  5. filter();
  6. every();
  7. some();

1.2偏函數用法

偏函數用法是指建立一個調用另一個部分-參數或者變量已經預置的函數-的函數的用法。多線程

例子:併發

var toString = Object.prototype.toString;
var isString = function (obj) {
	return toString.call(obj) == '[object string]';
};
var isFunction = function (obj) {
	return toString.call(obj) == '[object function]';
};
複製代碼

例子改進(工廠模式):異步

var isType = function (type) {
	return function (obj) {
		retirn toString.call(obj) == '[object' + type + ']';
	}
};
var isString = isType('String');
var isFunvtion = isType('Funvtion');
複製代碼

這種經過指定部分參數來產生一個新的定製的形式就是偏函數。async

2.異步編程的優點與難點

2.1優點

Node帶來的最大特性是基於事件驅動的非阻塞I/O模型。非阻塞I/O可使CPU與I/O並不相互依賴等待,讓資源更好的利用。對於網絡應用而言,並行帶來的想象空間巨大。延展而開是分佈式和雲。

因爲事件循環模型須要應對海量的請求,海量請求同時做用在單線程上,就須要防止任何一個計算耗費過多的CPU時間片。至因而計算密集型,仍是I/O密集型,只要計算不要影響異步I/O的調度,那就不構成問題。

2.2難點

難點:

  1. 異常處理;

    編寫異步方法遵循的原則:

    • 必須執行調用者傳入的回調函數;
    • 正常傳遞異常供調用者判斷;
  2. 函數嵌套過深;

  3. 阻塞代碼;

  4. 多線程編程;

    使用Web Workers,利用消息機制是合理使用多核CPU的理想模型。

    Web Workers可以解決利用CPU和減小阻塞UI渲染,可是不能解決UI渲染的效率問題。

  5. 異步轉同步;

3.異步編程解決方案

3.1事件發佈/訂閱模式

事件監聽器模式是一種普遍用於異步編程的模式,是回調函數的事件化,又稱發佈/訂閱模式。

事件發佈/訂閱模式:

// 訂閱
emitter.on("event1",function(message){
	console(message);
})
// 發佈
emitter.emit("event1","I am message!");
複製代碼

訂閱事件就是個高階函數的應用。事件發佈/訂閱模式能夠實現一個事件與多個回調函數的關聯,這些回調函數又稱爲事件偵聽器。

經過emit()發佈事件後,消息會當即傳遞給當前事件的全部偵聽器執行。偵聽器能夠很靈活地添加和刪除,使得事件和具體處理邏輯之間能夠很輕鬆關聯和解耦。

從另外一種角度看,事件偵聽器模式也是一種鉤子(hook)機制,利用鉤子導出內部數據或狀態給外部的調用者。

Node對事件發佈/訂閱的機制的額外處理:

  • 若是一個事件添加了超過10個偵聽器,將會獲得一個警告;

  • 爲了處理異常,EventEmitter對象對error事件進行了特殊對待;

  1. 繼承events模塊

Node中Stream對象繼承EventEmitter的例子:

var events = require('events');
function Stream (){
	events.EventEmitter.call(this);
}
util.inherits(Stream,events.EventEmitter);
複製代碼
  1. 利用事件隊列解決雪崩問題

once():偵聽器只能執行一次,在執行以後就會將它與事件的關聯移除。

採用once()解決雪崩問題。

雪崩問題:

在高訪問量、大併發量的狀況下緩存失效的情景,此時大量的請求同時涌入數據庫中,數據庫沒法同時承受如此大的查詢請求,進而往前影響網站的總體的響應速度。

一條數據庫查詢語句的調用:

var select = function (callback) {
	db.select("SQL",function (results) {
		callback(results);
	});
};
複製代碼
  1. 多異步之間的協做方案

通常而言,事件與偵聽器的關係是一對多,但在異步編程中,也會出現事件與偵聽器的關係是多對一的狀況,也就是說一個業務邏輯可能依賴兩個經過回調或事件傳遞的結果。回調嵌套過深的緣由就是如此。

因爲多個異步場景中回調函數的執行並不能保證順序,且回調函數之間沒有任何交集,因此須要藉助一個第三方函數和第三方變量來處理異步協做的結果。這個用於監測次數的變量叫作哨兵變量

  1. EventProxy的原理

    EventProxy來自於Backbone的事件模塊,Backbone的事件模塊是Model、View模塊的基礎功能。

    EventProxy將all當作一個事件流的攔截層,在其中注入一些業務來處理單一事件沒法解決的異步處理問題。

  2. EventProxy的異常處理

    EventProxy提供了fail()和done()這兩個實例方法來優化異常處理,使得開發者將精力關注在業務實現,而不是在異常捕獲上。

3.2Promise/Deferred模式

使用事件的方式時,執行流程須要被預先設定。即使是分支,也須要預先設定,這是由發佈/訂閱模式的運行機制所決定的。

是否有一種先執行異步調用,延遲傳遞處理的方式呢?

答案是Promise/Deferred模式。

  1. Promises/A
    • Promise操做只會處在:未完成態、完成態、和失敗態的一種;
    • Promise的狀態只能從未完成態向完成態或失敗態轉化,不能轉化。完成態和失敗態不能相互轉化;
    • Promise的狀態一旦轉化,將不能被更改;

一個Promise對象只要具有then()方法便可。

對於then()方法的要求:

  • 接收完成態、錯誤態的回調方法。在操做完成或者出現錯誤的時候,將會調用對應的方法;
  • 可選地支持progress事件回調做爲第三個方法;
  • then()方法只接受function對象,其他對象將被忽略;
  • then()方法返回的是Promise對象,支持鏈式調用;

then()方法所作的事情是將回調函數給存儲起來,爲了完成整個流程,還須要觸發執行這些回調函數的地方,實現這些功能的對象一般被稱爲Deferred,即延遲對象。

Promise和Dererred的差異:

Promise做用於外部,經過then()方法暴露給外部已添加自定義邏輯;

Dererred做用於內部,用於維護異步模型的狀態;

Promise是高級接口,事件是低級 接口。低級接口能夠構建更多更復雜的場景,高級接口一旦定義。不太容易變化,再也不有低級接口的靈活性。但對於解決典型問題很是有效。

Promise經過封裝異步調用,實現了正向用例和反向用例的分離以及邏輯處理延遲。

Promise須要封裝,可是強大,具有很強的侵入性,純粹的函數則較爲輕量,但功能相對較弱。

  1. Promise中的多異步協做

    相似於EventProxy。

  2. Promise的進階知識

    在API的暴露上,Promise模式比原始的事件偵聽和觸發略爲優美,缺陷是須要爲不一樣的場景封裝不一樣的API,沒有直接的原生事件那麼靈活。

    Promise的祕訣其實在於對隊列的操做。

    • 支持序列執行的Promise

      理想的編程體驗是前一個的調用的結果做爲下一個調用的開始,就是所謂的鏈式調用。

      要讓Promise支持鏈式執行的步驟:

      1. 將全部的回調都存在隊列中;
      2. Promise完成時,逐個執行回調,一旦監測到返回了新的Promise對象,中止執行,而後將當前的Deferred對象的promise引用改變爲新的Promise對象,並將隊列中餘下的回調轉交給他。
    • 將APIPromise化

3.3流程控制庫

  1. 尾觸發與Next

    尾觸發:

    須要手工調用才能持續執行後續調用的。

    常見的關鍵詞是Next.

    尾觸發目前應用最多的地方是Connect的中間件。

    中間件最簡單的例子:

    function (req,res,next) {
    	//中間件
    }
    複製代碼

    每一箇中間件傳遞請求對象、響應對象和尾觸發函數,經過隊列造成一個處理流。

    中間件機制使得在 處理網絡請求時,能夠像面向切面編程同樣進行過濾、驗證、日誌等功能,而不與具體業務邏輯產生關聯,以至產生耦合。

    原始的next()方法較爲複雜,簡化和的原理十分簡單,取出隊列的中間件並執行,同時傳入當前方法以實現遞歸調用,達到持續觸發的目的。

  2. async

    • 異步的串行執行
    • 異步的並行執行
    • 異步調用的依賴處理
    • 自動依賴處理
  3. Step

    比async更輕量。

    Step接收任意數量的任務,全部的任務都將會串行依次執行。

    Step與事件模式、Promise、async都不一樣的一點在於Step用到了this關鍵字。事實上,它是Step內部的一個next()方法,將異步的調用的結果傳遞給下一個任務做爲參數,並調用執行。

    • 並行任務執行
    • 結果分組
  4. wind

    wind爲JavaScript語言提供了一個monadic擴展,可以顯著提升一些常見場景下的異步編程體驗。

    • 異步任務定義
    • $await()與任務模型
    • 異步方法轉換輔助函數

4.異步併發控制

異步I/O與同步I/O的顯著差距:

  • 同步I/O由於每一個I/O都是彼此阻塞的,在循壞體中,老是一個接着一個調用,不會出現耗用文件描述符太多的狀況,同時性能也是低下的;

  • 異步I/O,雖然併發容易實現,可是因爲太容易實現,依然須要控制;

4.1 bagpipe的解決方案

bagpipe模塊的解決思路:

  • 經過一個隊列來控制併發量;
  • 若是當前活躍(指調用發起但未執行回調)的異步調用量小於限定值,從隊列中取出執行;
  • 若是活躍調用達到限定值,從隊列中取出新的異步調用執行;
  • 每一個異步調用結束時,從隊列中取出新的異步調用執行;

bagpipe相似於打開了一道窗口,容許異步調用並行進行,可是嚴格限定上限。僅僅在調用push()時分開傳遞,並不對原有API有任何侵入。

  • 拒絕模式
  • 超時控制

4.2 async的解決方案

async提供的處理異步調用的限制:parallelLimit()。

相關文章
相關標籤/搜索