2012年10月8日 星期一

Node.js 裡的 JavaScript Array 非同步操作

Standard
如果你原本是一個專業的前端工程師,或許在 Node.js 出來之後,便開始嘗試了 Server 程式的開發。然後發現,使用 JavaScript 開發 Server 程式的過程中,有很多的細節應該注意,尤其是碰上處理大量資料和 I/O 操作時,更要對 JavaScript 引擎的運作原理有一定程度的理解。因為,絕大多數的問題,都是出在非同步(Asynchronize)與同步(Synchronize)的機制上。

許多開發人員,都會認同在開發 Node.js 程式時,盡可能、甚至絕對不要使用『同步』的方法和 API,因為,那會打亂 JavaScript 事件引擎的運作,產生各種詭異的後疑症,而且非常難以除錯。如果你發現你的程式會莫明自己結束,或是程式運作的流程不如預期,可能就是掉入了『同步』與『非同步』的陷阱了。

如果你稍微翻過文件,就會發現,其實 Node.js 本身對很多的 API,已經同時提供了『同步』與『非同步』兩種版本供開發者使用。不過,雖然 Node.js API 都有提供『非同步』版本,但是有些根本東西,卻欠缺『非同步』的方法,像是本文標題所提到的『Array』,就是其中之一。

針對 Array 的諸多操作,最常見的莫過於 forEach 方法,用來處理每一個 Array item:
var arr = [ 1, 2, 3, 4 ];

arr.forEach(function(element, index, arr) {
    // Processing
});

console.log('Completed');
然而,這個方法是『同步(Synchronize)』,也就是會讓整個程式阻塞,等所有 Array item 讀出和處理完之後,才顯示最後的『Completed』字串。

類似的東西,不少人也會使用純 for-loop 來實作,但也都是一樣的狀況:
var arr = [ 1, 2, 3, 4 ];

for (var i in arr) {
    // Processing
}

console.log('Completed');

想想看,如果 Array item 的數量相當多,又或者是每個 item 的處理時間相當長,整支程式會像死當一樣沒反應。要知道,讓一段程式跑太久,是 Event-driven 的忌諱,更是 Server 程式的大敵,因為它會讓你的網站服務反應速度極慢。可是,這樣操作 Array 資料的需求,卻又是非常常見。

還好,多數人開始學習 Node.js 之後,因為對 Event-driven 的機制開始越來越熟悉,所以,開始能夠瞭解並利用各種非同步的方法,讓程式避免阻塞死鎖,增加效能和處理更多的連線要求。也因為如此,針對 Array 的遍歷操作,自然懂得開始使用遞迴的方式處理。但是,這樣常見的用法,時時都會出現,每次都要重新實作一次,豈不麻煩?

尋思,我們是否可以有個簡單的做法,以『非同步(Asynchronize)』的方法去操作 Array 呢?

當然,筆者自認是世界上最懶惰的人,所以,就為此開發出『node-array』,讓日後能少打點字:
https://github.com/cfsghost/node-array

安裝方法相當簡單,你可以直接從 NPM 上安裝:
npm install node-array

使用方法更簡單,node-array 提供了一個新的方法『forEachAsync()』,如 Array 原生的 forEach 一樣使用即可:
var Array = require('node-array');
    
var a = [ 1, 2, 3, 4, 5 ];
    
a.forEachAsync(function(element, index, arr) {
    console.log(element);
}, function() {
    console.log('complete');
});

從上面的程式碼可以知道,其實 node-array 是幫原生的 JavaScript Array Object 擴充功能,繼承了原生的 Array Object,並添加了一個新的方法進去。所以,你一但在開頭將 Array 用 node-array 取代掉,之後所有建立的 Array Object 都能有 forEachAsync() 方法可使用。從此,你不用再花心力去搞定遞迴和 process.nextTick() 了!

最後一提,node-array 初步只實作了最常用到的 forEach,日後也陸續會將其他常用的功能加入。如果您有什麼想法或 patch,歡迎提交給小弟。:-)

後記

解決了這類根本的問題後,並應用在實際開發後,意外發現了一個正面的副作用,就是程式碼漂亮很多,也『平坦』許多。讓我不禁思考,很多想把 JavaScript 程式碼平坦化的工作,是否真的必要?若是所有根本性的問題解決後,是否程式碼自然就不會太高聳?