1. 程式人生 > >淺談node的異步

淺談node的異步

rect 模仿 聽說 defined 事件 單線程 test 代碼 net

剛和朋友吃完飯,回來接著寫 ,異步,是node 中一個很重要的概念,可以說對於前端想要轉到後臺的來說(我這裏說的是沒有接觸過後臺的人來說),路由和異步還有包括node是如何建立web服務呈遞頁面的,這些東西很難轉變過來,特別對於經常寫js的人來說,來做node的話,可能經常就分不清楚哪裏寫的是前端代碼,那些寫的是後臺代碼了,這是思維模式方面的轉變,這個的確很難,特別如果前端接觸一段時間node會特別有感觸。

我本人最早是學。net後臺的,之後慢點接觸前端,學習js的一些技術,包括一些框架jquery,ng(angular等)還有bootstrap等,之後接觸到的node,對於我來說node中最大的不同之處就是在異步這一塊,node中的很多東西基本都是異步的,所以如果你不理解異步的話,很難深入學習node,可以說,這也是node思想的精髓所在,不然node的單線程是無法實現高並發的,所以想要學node,異步必須要了解。

其實我之前寫的用fs模塊讀取html文件的時候,這個fs模塊的讀取文件的過程就是異步的,當node遇到一個I/O的時候,他不會停下等這個I/O操作網,再去執行其他代碼,而是繼續去執行別的代碼,等到I/O操作完成),會觸發執行完的事前,調用執行完成時的回調函數,所以在這裏我們也可以理解node中的事件驅動,event loop,非阻塞I/O等概念了。

其實在咱們之前學的js中,就有異步的概念,我在ajax異步簡介這篇隨筆中還提到了的,那麽咱們可以簡單的看下異步的原理

var a=1;
setTimeout(function(){
	console.log(a);//2
}, 30000);
console.log(a);//1
a++;

  從上面可以看出,當js遇到定時器的時候,上面settimeout定時器定義了一個定時器,3秒後觸發內面的方法,當js遇到這個定時器的時候,js不會到這裏停3秒,等到定時器裏的方法觸發,而是繼續執行下面的代碼,等到3秒到後,觸發執行定時器內的方法,上面這個例子可以簡單的理解下異步的概念(不一定嚴謹,哈哈)。

當然這是js中的異步的一個簡單案例,可以這麽說node的牛逼的地方很大程度上在於異步上,劉邦說,成也蕭何,敗也蕭何。。那麽我們也可以說node成也異步,敗也異步(當然node還是很厲害的哈,至少在移動端盛行的今天,高並發的時後,node還是發揮了很重要的作用,手機淘寶聽說在上層就是用的node來處理高並發的,當然業務層可能是php,底層可能還是java和mysql等),好了,咱們回歸正題。

我之前說道,如果是老牌語言的後臺來學node,其他都很好理解,就是異步這塊特別別扭,因為業務始終是同步的,而在node中很多操作都是異步執行,這可能會導致某個業務會有很多層的嵌套,在代碼層面就是一層回調套一層回調,回調裏面在套回調,這樣從老牌後臺語言轉到node的人,就會感覺特別扭,而且業務復雜了後,代碼將也會很復雜(但是根本還是思想的轉變)。

好,我們來看下這樣的一個例子(看下node的異步)

var http = require(‘http‘);
var fs = require(‘fs‘);

var server = http.createServer(function(req,res){
	//我們做這樣一個場景,模仿用戶同時登錄系統,看看node中關於事件執行的機制和異步的I/O
	//從這個中可以看到node中事件是如何工作,node是如何在單線程的條件下有高度的並發的特點的
    var randomNum = Math.random()*1000 + 3212;
    console.log(‘歡迎用戶user_‘+randomNum);
    fs.readFile(‘test/1.txt‘,function(err,data){
    	console.log(‘用戶_user‘+randomNum+‘讀取完畢‘);

    })
    res.end();
});
server.listen(3010,‘127.0.0.1‘);

  我們然後不斷的刷新瀏覽器,觀察控制臺中的輸出,

技術分享圖片

很明顯我們看到了,顯示歡飲用戶4119後沒有立馬顯示4119讀取完畢,而是歡迎用戶3776,然後再是用戶4119讀取文件,所以這裏我們很明顯可以看到這裏的異步操作,用戶4119讀取文件時,服務並沒有阻塞,而是繼續相應用戶3776的請求,所以這就是node的非阻塞I/O,這也是node能夠實現高並發的依賴。

上述例子我們看到了fs的讀取數據的過程是異步的,所以我們要獲取讀取文件的信息,就必須在fs的回調函數中寫代碼,這和我之前在。net或php中的做法完全不同,在。net或php中都是通過摸個對象或方法,輸入文件路徑,和其他參數,然後返回給你一個字符串(文本的數據);

技術分享圖片

看到這樣一個api,於是直接擼代碼,開幹(app.js)

var fs = require(‘fs‘);

fs.readdir(‘./album‘,function(err,files){
	if(err){
		//獲取目錄下的文件信息失敗
		return;
	}
	//獲取目錄下的所有文件成功,files中包含了album文件夾下所有的文件信息
	var directorys = [];
	for (var i = 0; i < files.length; i++) {
		var file = files[i];
		fs.stat(‘./album/‘+file,function(err,stats){
			if(stats.isDirectory()){
				//是一個文件夾
				directorys.push(files[i]);
				console.log(directorys);
			}
		})
	};

	
})

  

輸出

技術分享圖片

很明顯,這不是我們想要的結果,那麽為什麽會出現兩個undefined的情況列,如果我們好好想想就明白了,fs模塊的操作是異步的,而我在這裏再for循環中嵌套了fs的操作,所以還沒等fs的操作完成,for循環就搞完了,這樣最後得到的i的值就是length,超出了files的界限了,files【i】自然就是=undefined。但是這幾本就是老牌後臺程序員的思維,所以我才認為這是對由其他後臺轉node的,需要轉變的思維的地方。(我之前也暈了好久,但是慢點多寫點就好了)

那麽正確的及解決方案是怎樣的了,我在這裏使用的是立即函數+遞歸實現類似於叠代器的功能,來實現的(簡單的說,就是強行將異步轉變同步,),上代碼體會就懂了話不多說

var fs = require(‘fs‘);

fs.readdir(‘./album‘,function(err,files){
	if(err){
		//獲取目錄下的文件信息失敗
		return;
	}
	//獲取目錄下的所有文件成功,files中包含了album文件夾下所有的文件信息
	var directorys = [];
	/*for (var i = 0; i < files.length; i++) {
		var file = files[i];
		fs.stat(‘./album/‘+file,function(err,stats){
			if(stats.isDirectory()){
				//是一個文件夾
				directorys.push(files[i]);
				console.log(directorys);
			}
		})
	};*/

	(function getDirectory(i){
		if(i == files.length){
			//讀取完畢
			console.log(directorys);
			return;
		}
		var file = files[i];
		fs.stat(‘./album/‘+file,function(err,stats){
			if(stats.isDirectory()){
				//是一個文件夾
				directorys.push(files[i]);
			}
			getDirectory(i+1);
		})
	})(0)
})

  

 輸出

技術分享圖片

縱欲得到了正確的結果,真的是太艱辛了,當然上面這種用立即函數的形式在前端中也經常會看到,這是我的解決方式,雖然,node中也有同步方法,在node的官網可以看到,node在很多異步操作的後面都要提供了相應的同步的操作,但是我始終感覺,node這樣做,是不是與自己的理念有點背道而馳(當然了,個人見解,高手勿噴,哈哈哈哈)。。

這就是我自己對異步的一點理解,當然我的理解都是從代碼層面上來說明的,可能有點淺顯(個人能力尚淺),我感覺只有理解好這些後,才能保證你的node代碼繼續寫下去,繼續學下去,否則很可能學到一定程度你就想地放棄node了,(因為前端用node可能更多的用來構建前端自動化和包管理方面),等你真真涉及後臺的一些思想和技術時,你就會發現有很大的不同的

淺談node的異步