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

如果你原本是一個專業的前端工程師,或許在 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 程式碼平坦化的工作,是否真的必要?若是所有根本性的問題解決後,是否程式碼自然就不會太高聳?

留言

  1. 老兄,你是真不懂还是做实验呢。。。

    github 上,caolan 有个 aysnc 库,里面有一系列异步 each map reduce 方法可以用,而且还可以方便地收集结果。

    https://github.com/caolan/async 那个库在这里。
    我针对这个库写的各种 demo 在这里:https://github.com/alsotang/async_demo。
    鉴于你实现的是 each 功能,请猛击这里:https://github.com/caolan/async#each

    (不好意思大陆人不会繁体字)

    回覆刪除
    回覆
    1. 我想文章裡已經清楚表達我的想法,我希望用最『直覺』的方式去寫 JavaScript,所以我實作的是與原生 forEach 使用方法一樣的 forEachAsync,然後是直接對 Array Object 擴展方法,純粹只是對 Array 做處理。

      外掛一個 Module,然後引入一堆功能,太過『痴肥且混亂』,不是我喜歡的 Coding Style。我喜歡什麼東西該需要什麼方法,就應該讓他自帶擁有,至樣寫出來的 Code 才清爽自然,不拖泥帶水。

      文章最後我也提到:『很多想把 JavaScript 程式碼平坦化的工作,是否真的必要?』。像 async 這類包山包海的 module,其實大多功能是『平常用不到』的。但不可否認 async 做了很多事,我們需要用到,但在這之前,我們需要做的是,把 async module 拆開來看看,哪些該歸類於 Array 基本應該有的功能,就應該是直接移植去擴展 Array,而不是多一個超大包的 async module。

      BTW, 這是我寫 Code 的理念,所以日後我有空時也仍然會寫個類似 node-array 的 node-object(針對 key/value Object 做擴展) 和 Async-loop(for-loop, while... etc)。如果您有興趣,歡迎一同開發。:-)

      刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(二)

淺談 USB 通訊架構之定義(一)

Reverse SSH Tunnel 反向打洞實錄