Node是首個將異步大規模帶到應用層面的平臺,它從內在運行機制到API的設計,無不透露出異步的氣息來。數據庫
在JavaScript中,函數做爲一等公民,使用上很是自由,不管調用它,或者做爲參數,或者做爲返回值都可。編程
函數式編程是JavaScript異步編程的基礎。promise
高階函數把函數做爲參數,或是將函數做爲返回值的函數。緩存
function Night(x){
return function (){
return x;
}
}
複製代碼
事件的處理方式正是基於高階函數的特性的靈活性來完成的。bash
ECMAScript5提供的高階函數:網絡
偏函數用法是指建立一個調用另一個部分-參數或者變量已經預置的函數-的函數的用法。多線程
例子:併發
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
Node帶來的最大特性是基於事件驅動的非阻塞I/O模型。非阻塞I/O可使CPU與I/O並不相互依賴等待,讓資源更好的利用。對於網絡應用而言,並行帶來的想象空間巨大。延展而開是分佈式和雲。
因爲事件循環模型須要應對海量的請求,海量請求同時做用在單線程上,就須要防止任何一個計算耗費過多的CPU時間片。至因而計算密集型,仍是I/O密集型,只要計算不要影響異步I/O的調度,那就不構成問題。
難點:
異常處理;
編寫異步方法遵循的原則:
函數嵌套過深;
阻塞代碼;
多線程編程;
使用Web Workers,利用消息機制是合理使用多核CPU的理想模型。
Web Workers可以解決利用CPU和減小阻塞UI渲染,可是不能解決UI渲染的效率問題。
異步轉同步;
事件監聽器模式是一種普遍用於異步編程的模式,是回調函數的事件化,又稱發佈/訂閱模式。
事件發佈/訂閱模式:
// 訂閱
emitter.on("event1",function(message){
console(message);
})
// 發佈
emitter.emit("event1","I am message!");
複製代碼
訂閱事件就是個高階函數的應用。事件發佈/訂閱模式能夠實現一個事件與多個回調函數的關聯,這些回調函數又稱爲事件偵聽器。
經過emit()發佈事件後,消息會當即傳遞給當前事件的全部偵聽器執行。偵聽器能夠很靈活地添加和刪除,使得事件和具體處理邏輯之間能夠很輕鬆關聯和解耦。
從另外一種角度看,事件偵聽器模式也是一種鉤子(hook)機制,利用鉤子導出內部數據或狀態給外部的調用者。
Node對事件發佈/訂閱的機制的額外處理:
若是一個事件添加了超過10個偵聽器,將會獲得一個警告;
爲了處理異常,EventEmitter對象對error事件進行了特殊對待;
Node中Stream對象繼承EventEmitter的例子:
var events = require('events');
function Stream (){
events.EventEmitter.call(this);
}
util.inherits(Stream,events.EventEmitter);
複製代碼
once():偵聽器只能執行一次,在執行以後就會將它與事件的關聯移除。
採用once()解決雪崩問題。
雪崩問題:
在高訪問量、大併發量的狀況下緩存失效的情景,此時大量的請求同時涌入數據庫中,數據庫沒法同時承受如此大的查詢請求,進而往前影響網站的總體的響應速度。
一條數據庫查詢語句的調用:
var select = function (callback) {
db.select("SQL",function (results) {
callback(results);
});
};
複製代碼
通常而言,事件與偵聽器的關係是一對多,但在異步編程中,也會出現事件與偵聽器的關係是多對一的狀況,也就是說一個業務邏輯可能依賴兩個經過回調或事件傳遞的結果。回調嵌套過深的緣由就是如此。
因爲多個異步場景中回調函數的執行並不能保證順序,且回調函數之間沒有任何交集,因此須要藉助一個第三方函數和第三方變量來處理異步協做的結果。這個用於監測次數的變量叫作哨兵變量。
EventProxy的原理
EventProxy來自於Backbone的事件模塊,Backbone的事件模塊是Model、View模塊的基礎功能。
EventProxy將all當作一個事件流的攔截層,在其中注入一些業務來處理單一事件沒法解決的異步處理問題。
EventProxy的異常處理
EventProxy提供了fail()和done()這兩個實例方法來優化異常處理,使得開發者將精力關注在業務實現,而不是在異常捕獲上。
使用事件的方式時,執行流程須要被預先設定。即使是分支,也須要預先設定,這是由發佈/訂閱模式的運行機制所決定的。
是否有一種先執行異步調用,延遲傳遞處理的方式呢?
答案是Promise/Deferred模式。
一個Promise對象只要具有then()方法便可。
對於then()方法的要求:
then()方法所作的事情是將回調函數給存儲起來,爲了完成整個流程,還須要觸發執行這些回調函數的地方,實現這些功能的對象一般被稱爲Deferred,即延遲對象。
Promise和Dererred的差異:
Promise做用於外部,經過then()方法暴露給外部已添加自定義邏輯;
Dererred做用於內部,用於維護異步模型的狀態;
Promise是高級接口,事件是低級 接口。低級接口能夠構建更多更復雜的場景,高級接口一旦定義。不太容易變化,再也不有低級接口的靈活性。但對於解決典型問題很是有效。
Promise經過封裝異步調用,實現了正向用例和反向用例的分離以及邏輯處理延遲。
Promise須要封裝,可是強大,具有很強的侵入性,純粹的函數則較爲輕量,但功能相對較弱。
Promise中的多異步協做
相似於EventProxy。
Promise的進階知識
在API的暴露上,Promise模式比原始的事件偵聽和觸發略爲優美,缺陷是須要爲不一樣的場景封裝不一樣的API,沒有直接的原生事件那麼靈活。
Promise的祕訣其實在於對隊列的操做。
支持序列執行的Promise
理想的編程體驗是前一個的調用的結果做爲下一個調用的開始,就是所謂的鏈式調用。
要讓Promise支持鏈式執行的步驟:
將APIPromise化
尾觸發與Next
尾觸發:
須要手工調用才能持續執行後續調用的。
常見的關鍵詞是Next.
尾觸發目前應用最多的地方是Connect的中間件。
中間件最簡單的例子:
function (req,res,next) {
//中間件
}
複製代碼
每一箇中間件傳遞請求對象、響應對象和尾觸發函數,經過隊列造成一個處理流。
中間件機制使得在 處理網絡請求時,能夠像面向切面編程同樣進行過濾、驗證、日誌等功能,而不與具體業務邏輯產生關聯,以至產生耦合。
原始的next()方法較爲複雜,簡化和的原理十分簡單,取出隊列的中間件並執行,同時傳入當前方法以實現遞歸調用,達到持續觸發的目的。
async
Step
比async更輕量。
Step接收任意數量的任務,全部的任務都將會串行依次執行。
Step與事件模式、Promise、async都不一樣的一點在於Step用到了this關鍵字。事實上,它是Step內部的一個next()方法,將異步的調用的結果傳遞給下一個任務做爲參數,並調用執行。
wind
wind爲JavaScript語言提供了一個monadic擴展,可以顯著提升一些常見場景下的異步編程體驗。
異步I/O與同步I/O的顯著差距:
同步I/O由於每一個I/O都是彼此阻塞的,在循壞體中,老是一個接着一個調用,不會出現耗用文件描述符太多的狀況,同時性能也是低下的;
異步I/O,雖然併發容易實現,可是因爲太容易實現,依然須要控制;
bagpipe模塊的解決思路:
bagpipe相似於打開了一道窗口,容許異步調用並行進行,可是嚴格限定上限。僅僅在調用push()時分開傳遞,並不對原有API有任何侵入。
async提供的處理異步調用的限制:parallelLimit()。