玩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
有人看不下去了,便自會有人站出來,咱們漸漸地實現了從無到有的過程,我最開始接觸的是阿里的瀏覽器
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是國外強大的異步模塊,它的功能與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的使用代碼感覺感覺
//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去回調本次返回的結果。
首先使用了方法:
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方法並不會跟着執行。
固然咱們也能夠自定義多個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模塊也是一個很是優秀的Promise,它的實現原理和bluebird都大同小異,都是基於Promise/A+標準來擴展的,因此使用上甚至都是差不了多少的,選擇哪個就看我的愛好了。
重點來啦,咱們先來看一段普通的代碼
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
方法調用時,若是varibale
爲undefined
,那麼我則去作一個讀文件的操做,從文件中將它讀出來,並反回,你會怎麼實現呢?
你會發現,經過以往的思惟,你是沒法作到這一方法的,那麼使用異步思惟去想一想呢,好像有點門頭:
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.