1.2 堆棧追蹤
在轉換過程中,會看到很多的堆棧信息,非常之多。以下示例顯示了運行httpsvr.njs時,node-static npm包未安裝導致的錯誤以及生成的堆棧信息:
module.js:337 throw new Error("Cannot find module '"+ request + "'"); ^ Error: Cannot find module 'node-static' at Function._resolveFilename (module.js:337:11) at Function._load (module.js:279:25) at Module.require (module.js:359:17) at require(module.js:375:17) at Object.<anonymous> (httpsvr.njs:2:14) at Module._compile(module.js:446:26) at Object..js (module.js:464:10) at module.load (module.js:353:31) at Function._load (module.js:311:12) at Array.0 (module.js:484:10)
堆棧信息的最上面一行顯示了拋出異常的代碼所在。這並不是産生錯誤的代碼,而是創建並拋出error對象的代碼。
這行之後,顯示的是Error對象内部的錯誤信息。這個錯誤信息告訴了我們沒有找到node-static模塊。
其餘的部分稱爲“調用棧”,是一系列由“at”開頭的鏈式函數調用,直到到達拋出異常的代碼。調用棧按照從内到外的順序排列。在本例中,Function._resolveFilename()函數是調用棧的最頂耑,表示它是最内層調用的函數,直接包含了拋出異常的代碼。._resolveFilename()函數被Function._load()函數調用,該函數又被Module.require()函數調用,Module.require()又被require()函數函數,再上層就是Object.<anonymous>()函數,依次類推。
在調用棧中每個函數的調用之後,你都會看到包含該函數的源文档名,最後一行執行的代碼(可能是調用函數之前的一行或者拋出異常的那行),以及該行代碼中最後的執行位置。在上個例子中,可以看到涉及的兩個文档是:module.js和httpsvr.njs。
module.js文档存在於node命令中,我們並不認爲這是屬於我們自己的代碼文档,而httpsvr.njs則是我們自己的源代碼。盡管調用棧中只顯示了一次httpsvr.njs,但是可以認爲引起錯誤的是我們自己的代碼。一般來說,我們認爲Node.js本身,其内建的模塊,和其他安裝好的npm模塊都是安全可靠的。即使它們不是,我們也必須假定它們是正常工作的,除非我們证明排除了所有由我們的代碼産生錯誤的可能。但是,即使我們發現了錯誤是由其他原因導致的,我們能控制的也只有我們自己的源代碼。解決方式也應該是在我們的代碼中找到變通方案而不是通過漫長而緩慢的過程找到其他開發人員來修改他們的代碼。所以,結果是,不管最終的錯誤是什麽,首先需要關注的都是httpsvr.njs文档。
我們需要關注的調用棧中的部分是:
Object.<anonymous> (httpsvr.njs:2:14)
函數調用在httpsvr.njs的第2行第14個位置。Httpsvr.njs文档爲:
var http = require('http'); var static = require('node-static'); var file = new static.Server(); http.createServer(function(req,res){ file.serve(req,res); }).listen(1337,'127.0.0.1'); console.log('Server running ai http://127.0.0/');
通過在源代碼中交叉引用調用,用於加載的node-static模塊的require()函數就是錯誤發生的地方。這和錯誤信息“Cannot find module ‘node-static’”一致。
如果我們再看看調用棧,可以看到棧上方的Function._load()函數和_resolveFilename()函數。看看這兩個函數名,我們可以猜測Node.js環境加載該模塊出錯是因爲找不到與模塊相關的文档。所以可以做出假設找不到模塊文档(可能是npm包)是因爲模塊沒有安裝。再一次,這驗证了錯誤信息“Cannot find module ‘node-static’”。
Object.<anonymous>函數暗示我們,require()函數的調用是在全局範圍内,而不是httpsvr.njs用戶自定義範圍。但是事實並不一定都是這樣。一個匿名對象也有可能在用戶自定義的函數内生成。但是,繼續看調用棧,在Object.<anonymous>函數調用之後,我們看到調用者是module.js中的Module._compile函數。所以,require()函數是全局範圍内的調用。
綜合以上所有信息,一個解決方案是安裝node-static npm package:
npm install node-static
誠然,不需要每次在看Node.js調用棧時都做這些分析。但是你可能會看到很多很多的調用棧,你應該知道如何進行分析——特別是因爲找到並修複錯誤佔據了轉換過程95%的時間。
總結一下,分析一個調用棧信息的過程是:找到錯誤,查看錯誤信息(如果有的話),做出假設並關注在自己代碼中的特定函數調用,閲讀代碼並找出錯誤可能産生的位置,查看調用棧是否包含更多錯誤的可能信息,根據堆棧信息理解服務器如何執行到該調用函數。