連接:http://www.runoob.com/nodejs/nodejs-callback.htmlhtml
首先什麼是單線程異步非阻塞?node
單線程的意思整個程序從頭至尾可是運用一個線程,程序是從上往下執行的。異步操做就是程序雖然是從上到下執行的,可是某個函數執行時間過長時並不會阻塞在那裏等待它執行完,而後在執行下面的代碼。非阻塞也就是這個意思。linux
爲何node是異步非阻塞的呢,得力於回調函數,還有js中的定時器也是經典的異步操做。程序員
###4.1 Node.js異步機制 因爲異步的高效性,node.js設計之初就考慮作爲一個高效的web服務器,做者理所固然地使用了異步機制,並貫穿於整個node.js的編程模型中,新手在使用node.js編程時,每每會羈絆於因爲其餘編程語言的習慣,好比C/C++,以爲無所適從。咱們能夠從如下一段簡單的睡眠程序代碼窺視出他們的區別,下面是摘自《linux程序設計》打印10個時間的C代碼:web
#include <time.h> #include <stdio.h> #include <unistd.h> int main() { int i; time_t the_time; for(i = 1; i <= 10; i++) { the_time = time((time_t *)0); printf("The time is %ld\n", the_time); sleep(2); } exit(0); }
編譯後打印結果以下:編程
The time is 1396492137服務器
The time is 1396492139併發
The time is 1396492141異步
The time is 1396492143編程語言
The time is 1396492145
The time is 1396492147
The time is 1396492149
The time is 1396492151
The time is 1396492153
The time is 1396492155
從C語言的打印結果能夠發現,是隔2秒打印一次,按照C程序該有的邏輯,代碼逐行執行。如下Node.js代碼本意如同上述C代碼,使用目的隔2秒打印一次時間,共打印10條(初次從C/C++轉來接觸Node.js的程序員可能會寫出下面的代碼):
function test() { for (var i = 0; i < 10; i++) { console.log(new Date); setTimeout(function(){}, 2000); //睡眠2秒,而後再進行一下次for循環打印 } }; test();
打印結果: Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
Tue Apr 01 2014 14:53:22 GMT+0800 (中國標準時間)
觀察結果發現都是在14:53:22同一個時間點打印的,根本就沒有睡眠2秒後再執行下一輪循環打印!這是爲何?從官方的文檔咱們看出setTimeout是第二個參數表示逝去時間以後在執行第一個參數表示的callback函數,所以咱們能夠分析, 因爲Node.js的異步機制,setTimeout每一個for循環到此以後,都註冊了一個2秒後執行的回調函數而後當即返回立刻執行console.log(new Date),致使了全部打印的時間都是同一個點,所以咱們修改for循環的代碼以下:
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(new Date); }, 2000); }
執行結果以下所示: Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:30:35 GMT+0800 (中國標準時間) 神奇,仍然是同一個時間點,見鬼!冷靜下來分析,時刻考慮異步,for循環裏每次setTimeout註冊了2秒以後執行的一個打印時間的回調函數,而後當即返回,再執行setTimeout,如此反覆直到for循環結束,由於執行速度太快,致使同一個時間點註冊了10個2秒後執行的回調函數,所以致使了2秒後全部回調函數的當即執行。 咱們在for循環以前添加console.log("before FOR: " + new Date)和以後console.log("after FOR: " + new Date),來驗證咱們的推測,打印結果以下(後面省略8條相同的打印行): before FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中國標準時間)
after FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:42:45 GMT+0800 (中國標準時間)
Thu Apr 03 2014 09:42:45 GMT+0800 (中國標準時間) …… (省略與上一行8條相同的打印行) 由此能夠窺視出Node.js異步機制的端倪了,在for循環中的代碼於其後的代碼幾乎在一個單位秒內完成,而定時器中的回調函數則按要求的2秒以後執行,也是同一秒內執行完畢。那麼如何實現最初C語言每隔2秒打印一個系統時間的需求函數呢,我實現了以下一個wsleep函數,放在for循環中,能夠達到該目的:
function wsleep(milliSecond) { var startTime = new Date().getTime(); while(new Date().getTime() <= milliSecond + startTime) { } }
可是該函數有一個令他沒法在項目中使用的缺陷,請問爲何?
若是沒有回調函數的話,就變成阻塞式的,程序基本上是從上到下執行的。主要就是由於回調函數的影響致使的
2.阻塞與非阻塞
Node.js 異步編程的直接體現就是回調。
異步編程依託於回調來實現,但不能說使用了回調後程序就異步化了。
回調函數在完成任務後就會被調用,Node 使用了大量的回調函數,Node 全部 API 都支持回調函數。
例如,咱們能夠一邊讀取文件,一邊執行其餘命令,在文件讀取完成後,咱們將文件內容做爲回調函數的參數返回。這樣在執行代碼時就沒有阻塞或等待文件 I/O 操做。這就大大提升了 Node.js 的性能,能夠處理大量的併發請求。
建立一個文件 input.txt ,內容以下:
菜鳥教程官網地址:www.runoob.com
建立 main.js 文件, 代碼以下:
var fs = require("fs"); var data = fs.readFileSync('input.txt'); console.log(data.toString()); console.log("程序執行結束!");
以上代碼執行結果以下:
$ node main.js 菜鳥教程官網地址:www.runoob.com 程序執行結束!
建立一個文件 input.txt ,內容以下:
菜鳥教程官網地址:www.runoob.com
建立 main.js 文件, 代碼以下:
var fs = require("fs"); fs.readFile('input.txt', function (err, data) { if (err) return console.error(err); console.log(data.toString()); }); console.log("程序執行結束!");
以上代碼執行結果以下:
$ node main.js 程序執行結束! 菜鳥教程官網地址:www.runoob.com
以上兩個實例咱們瞭解了阻塞與非阻塞調用的不一樣。第一個實例在文件讀取完後才執行完程序。 第二個實例咱們呢不須要等待文件讀取完,這樣就能夠在讀取文件時同時執行接下來的代碼,大大提升了程序的性能。
所以,阻塞按是按順序執行的,而非阻塞是不須要按順序的,因此若是須要處理回調函數的參數,咱們就須要寫在回調函數內。