標籤:

為什麼nodejs的module.js里用了readFileSync而不用readFile?

// Native extension for .js
Module._extensions[".js"] = function(module, filename) {
var content = fs.readFileSync(filename, "utf8");
module._compile(internalModule.stripBOM(content), filename);
};

// Native extension for .json
Module._extensions[".json"] = function(module, filename) {
var content = fs.readFileSync(filename, "utf8");
try {
module.exports = JSON.parse(internalModule.stripBOM(content));
} catch (err) {
err.message = filename + ": " + err.message;
throw err;
}
};

為什麼會選擇使用同步而不用非同步實現呢。


如 @魯小夫 所說,這是因為 require() 是同步的。

進一步說,之所以同步是 Node.js 所遵循的 CommonJS 的模塊規範要求的。

在當年,CommonJS 社區對此就有很多爭議,導致了堅持非同步的 AMD 從 CommonJS 中分裂出來。

CommonJS 模塊是同步載入和同步執行,AMD 模塊是非同步載入和非同步執行,CMD(Sea.js)模塊是非同步載入和同步執行。ES6 的模塊體系最後選擇的是非同步載入和同步執行。也就是 Sea.js 的行為是最接近 ES6 模塊的。不過 Sea.js 這樣做是需要付出代價的——需要掃描代碼提取依賴,所以它不像 CommonJS/AMD 是純運行時的模塊系統。

注意 Sea.js 是 2010年之後開發的,提出 CMD 更晚。Node.js 當年(2009年)只有 CommonJS 和 AMD 兩個選擇。就算當時已經有 CMD 的等價提案,從性能角度出發,Node.js 不太可能選擇需要靜態分析開銷的 類 CMD 方案。考慮到 Node.js 的模塊是來自於本地文件系統,最後 Node.js 選擇了看上去更簡單的 CommonJS 模塊規範,直到今天。


我不懂NodeJS,所以說錯了我就再補充好了

為什麼用readFileSync,因為require函數被要求是同步的,所以後面主要說為什麼require是同步的

誠然我們可以說因為CommonJS是同步的,而NodeJS遵循CommonJS規範所以它是同步的

但這顯然沒有任何意義,為什麼NodeJS要選擇CommonJS,以Issac這麼固執的思路和NodeJS在正式發布前就存在的影響力而言,這貨自己造一套模塊規範都不成問題,你看在非同步的方案選型上NodeJS不就玩出了清新脫俗的單個callback模式么,更別說堅持node_modules目錄不能變更一輩子的信仰(不能再說了,不然黑不完了……)

所以到底為什麼require就得是要同步的呢,我們可以從不同的角度來看這事情

從模塊規範的角度來看,依賴的同步獲取是幾乎所有模塊機制的首選,是符合由無數的語言奠定的開發者的直覺的。即便以非同步為名的AMD,其模塊內部的依賴在大部分場景下也是同步的,只有按需載入或者總入口等幾個特殊場景才會使用非同步的require

因此,NodeJS的require是同步的,這是自然而然的選擇

但是NodeJS本身是非同步為基礎的啊,在經過NodeJS社區和各位傳道者孜孜不倦的教誨後,我們已經接受了一個公理:IO操作不非同步是很糟糕的。所以會有這個問題,你看NodeJS自己在糟糕著

是的,同步的IO操作會導致在NodeJS上出現一些問題,但這些問題是會在module這個模塊里遇到並且成為瓶頸的嗎,我不認為

我們看一下模塊有個什麼樣的特點:

  1. 一個系統的模塊是有限的,除非有人神經了創造出一個用模塊代替數據的玩法
  2. 模塊是有緩存的,即多次的require不會產生多次的IO操作

以上面2點為前提,我們也可以有一個結論:

在一個系統的運行周期內,require產生的IO操作次數是有限的

這不同於其它的IO(網路、文件、子進程),是會隨著系統的運行和輸入不斷積累增加的

而有限的IO操作是很難成為系統的瓶頸的,如果你說無論怎麼樣在系統運行的過程任何Block IO都不應該出現,那我們可以找遍所有的require在合適的時機進行處理,比如系統啟動時的預熱

你說預熱太麻煩?事實上從模塊到Class Loader,到資料庫鏈接,到內存緩存,到分級硬碟緩存,到虛擬機預跑,預熱就是大型系統的一部分,不爽不要玩

以上,是從模塊本身的特性來說的,結論就是使用非同步的require收益很小,同時對開發者並不友好

但我們總不能避免會有很極端的開發者存在,人家牛到可以接受非同步的依賴管理,可以追求極致的Non Blocking IO,並且人家想要任何可能存在的收益

這時候我們就要算ROI了,對NodeJS的實現者來說,其ROI夠嗎

可能很多人並不知道,NodeJS最開始是有非同步的require的,叫做require.async,直到0.3.0還是0.0.3版本才被移除

在NodeJS社區當時也是有過對移除require.async的討論Issue的,只是我現在找不到了,自從Node和IO合併以後很多以前的資料都不好找,無論是在archive repo還是在當前的repo都不好找……

require.async的實現其實是很麻煩的,我們就舉一個最稀鬆平常的案例:

一個require.async引起的readFile還沒結束時,又使用require.async獲取同一個模塊怎麼辦

產生2次readFile操作嗎?這顯然有了額外的開銷,同時一個模塊被執行2次是不可以的,有些模塊的執行是有副作用的

復用第一次的readFile嗎?這就需要一個callback的管理機制,這就是降低ROI的地方了

這只是一個普通的案例,你不如試想如果是一個require.async進行的過程中出現一個同步的require怎麼辦,這回可真是玩脫了……

而類似的邊界情況還有很多很多,這導致require.async這個函數相當的不穩定,很容易在各種順序的調用中出現奇怪的狀況,那麼直接移掉這鬼東西也是可以理解的

綜上,所以我認為Node的require同步是非常合理的選擇,因此推導出module.js里用readFileSync也是非常合理的選擇


因為 require() 載入模塊是同步操作


nodejs 讀本地文件速度快啊。用不用非阻塞io都不太影響啊。

像requirejs這樣的網路時延慢的就用非同步啦,不會阻塞住可以並行搞。


不會阻塞住可以並行搞


用不用非阻塞io都不太影響啊。


require一般是在腳本第一次執行時執行的,這時應用程序應該在啟動階段,對性能要求不高,所以同步非同步無所謂啦~哪種方法容易用就用哪種。


這是 node.js 的一個設計缺陷


推薦閱讀:

想學習nodejs 有什麼書可以推薦的?
《深入淺出Node.js》《Node.js 實戰(雙色)》《了不起的Node.js》 這三本書那本書比較好呢?
怎麼才能成為一個nodejs大神?
ECMAScript 6 的模塊相比 CommonJS 的require (...)有什麼優點?
a=new b()和a=b(),其本質的區別在哪?

TAG:JavaScript | Nodejs |