本人也是Javascript新手,把本身這段時間學習積累的要點總結下來,但願能夠對一樣在學習Javascript/node.js的同窗有一些參考價值。儘可能用通俗的語言幫助你們理解,若是有描述或理解不許確的地方歡迎你們指正,交流。另外本文假定你已經對javascript的語法和異步有一些基本的概念。
本系列會按通常學習異步編程的順序,首先介紹一下異步的原理,而後介紹各類異步編程的方法,從回調函數開始,而後慢慢進入Promise和Generator等對異步編程體驗進行改進的技術。期間也會大概提一下事件監聽方式的異步調用,可是由於太多的事件監聽會讓程序流程變的不清晰,因此不太推薦(其實事件模式本質上也是回調函數)。Promise和Generator纔是到目前爲止(ES6),最好的異步編程方式。後面也會分享一下本身所理解的這兩種方式的對比(網上也有不少爭論)。ES7提出了更好的改進方案,但願很快能夠抽出時間研究一下,這是後話:)
好,進入本節的正文。
什麼是異步?同步異步與阻塞非阻塞有什麼關係?
node.js的「一切皆異步」的思想頗有創意,目的是可讓開發者輕鬆編寫高性能的web服務端,而不會「不當心」就用同步api阻塞了服務器從而影響性能。其餘的語言好比php, python, java等基於同步的語言,雖然也有異步api,但畢竟編程人員的「思想上是同步的」,有時候不可避免的會寫出阻塞的代碼,node.js的目標是造就「思想上是徹底異步」的編程人員和編程語言:)
異步跟同步最大的不一樣就是異步api或函數被「調用」後不會等它運行結束再執行它後面的代碼,而是調用以後直接往下執行,異步函數的「執行」其實是放在「其餘地方」,待「執行」完成後再把結果經過回調函數來進行進一步的使用或處理(因此異步函數書寫的時候不要用"return"來返回值哦,必須經過回調函數來返回值)。這裏爲何強調「調用」和「執行」兩個詞呢?就是爲了更好的理解異步的過程。
打個比喻,你的領導要作個電子報表,笨領導的作法是,在本身電腦上把該統計的統計了,該算的算了,最後生成一張報表,而後用這個報表作接下去的工做;聰明的領導,會發個郵件給下屬,把報表的要求寫清楚,下屬在本身的電腦上把報表作完後,發個郵件把報表交回給領導。在下屬作報表的時候,領導能夠在本身電腦上繼續作其餘事情,好比玩遊戲、看視頻等等(你懂的)。
在上面這個例子中,領導是編程者(你),領導的電腦是當前線程,下屬的電腦是另外一個線程(若是有多個下屬就至關於有個線程池)。作報表這件工做是個異步函數,發郵件給下屬至關於調用這個函數,下屬電腦上作報表至關於在另外一個線程異步執行這個函數,執行完了發郵件把報表發回給領導至關於調用回調函數,領導就可使用這個報表接着作下面的工做(至關於回掉函數裏面的代碼)。下屬作報表的時候領導徹底不用管而是能夠繼續幹其餘事情。
經過這個例子能夠清楚的看到,領導只能在他本身的電腦(用戶線程)上工做,異步的函數都是在下屬的電腦上(異步線程)作的。這一點在不少文章當中並無講的很清楚,因此容易形成困擾,由於不少人只是一味的強調javascript是單線程的,但單線程怎麼能實現異步呢?就並無講清楚。其實所謂的單線程是指用戶線程是單線程,而另外還有一個或多個線程處理異步代碼的執行。
接下來再說說阻塞的問題。不少文章裏面在講解的時候同步異步阻塞非阻塞混爲一團,新手很難理解,最容易產生的誤解就是同步=阻塞,異步=非阻塞。其實阻塞非阻塞跟同步異步沒有任何關係。簡單講,阻塞就是一個api或者函數運行時間過長,而獨佔cpu致使其餘代碼不能運行,那麼多長時間算阻塞呢?相信這只是一個相對的概念,只要明顯影響到程序的性能和用戶體驗,就算阻塞吧。那跟同步異步是什麼關係呢?阻塞的代碼若是同步執行就會阻塞到本身後面代碼的運行,因此天然而然的就想到異步來執行阻塞的代碼,然而異步真能解決阻塞問題嗎?下面繼續講。
node.js裏面的異步
你們知道Javascript的基本語法跟其餘編程語言大同小異,最大的不一樣就是把異步擺在首位,特別是node.js更是大部分api都是異步的,小量同步api。這與其餘大部分語言恰好相反,也給習慣於同步編程思惟的同窗形成了很大的困擾,就是常說的轉不過彎來,習慣性的認爲代碼是按順序一行一行的往下執行。
上面介紹了異步非阻塞的概念,那麼具體到node.js是怎麼實現的呢?這裏又會涉及到I/O的概念,具體不講I/O的細節(操做系統原理都會講),關鍵就一點,I/O操做一般比較耗時但不會獨佔CPU,典型的I/O好比文件讀寫,遠程數據庫讀寫,網絡請求等。 先講耗時,若是用同步API來進行I/O操做,在返回結果以前就只能等待,因此最好的辦法上面已經講過,就是進行異步操做。接着說一下不會霸佔CPU的好處。在node.js進程裏面,有一個用戶線程(javascript所宣稱的單線程)和一個異步線程池(用戶沒法直接訪問), 若是跑在異步線程上的代碼是阻塞的,那麼這種異步根本就起不到消除阻塞的做用,爲何?緣由就是阻塞代碼會霸佔cpu,致使本進程全部代碼都等待不論是哪一個線程。可是,,,剛剛講的node.js裏面的I/O API都是不會霸佔CPU的,因此是非阻塞的,就不會出現這個問題。這就是node.js的最引覺得傲的特性之一:異步非阻塞I/O.
上面只是強調異步非阻塞,那麼對於真正的阻塞代碼,node.js怎麼辦呢?很差意思。。。單個node.js進程真無能爲力,跟同步編程語言相比沒什麼優點,只能用傳統的方式,多進程,多開幾個node.js進程,甚至多開幾個服務器。任何語言都有它的強項和弱項,node.js的強項就是它原本的設計初衷:讓開發者可以輕鬆的編寫高性能的web服務器(進行的最多的就是網絡和數據庫的I/O操做),而不是作大量的CPU密集型的運算(不過我趕腳,就算要作不少阻塞操做,用多進程和多服務器又有何不可?node.js對此提供了足夠的支持)。這樣算是回答了上一小節最後的問題了吧:)
要理解javascript異步編程和其餘語言同步編程的區別,能夠從一個最簡單的例子開始。在同步爲主的語言中,若是須要等待10秒鐘,一般是相似sleep 10之類的語句,在10秒以內整個進程掛起,也就是阻塞10秒。但javascript不是這樣,它是使用setTimeout函數,從字面意思就已經能夠看出區別了, 設一個timeout的時間, 在這段時間內cpu能夠繼續運行其餘代碼, 等10秒時間到了, 就用回調函數的形式來作應該10秒以後才作的事情。
附註:也許是我讀得書少:) 這麼久以來沒有發現網上有對異步編程講解的很透徹的文章,在本身學習過的資料當中,樸靈的《深刻淺出node.js》是講解的最深刻透徹的,強烈推薦。