JavaScript Errors and Stack Traces in Depthhtml
棧是一個後進先出LIFO (Last in,First out)的數據結構。調用堆棧實際上就是一個方法列表,按調用順序保存全部在運行期被調用的方法。調用堆棧會將當前正在執行的函數調用壓入堆棧,一旦函數調用結束,又會將它移出堆棧。node
用console.trace()
編寫一個簡單的例子來演示一下api
function c() { console.log('c'); console.trace(); } function b() { console.log('b'); c(); } function a() { console.log('a'); b(); } a();
咱們能夠看到console
輸出的結果瀏覽器
console.trace c @ VM59:3 b @ VM59:7 a @ VM59:11 (anonymous) @ VM59:13
咱們調用console.trace()
是在c
方法裏,這個時候c
還在執行,並無返回,所以從console
就能看到調用堆棧頂就是c
。咱們能夠稍微改變一下,好比把console.trace()
放在b
方法裏調用,以下:數據結構
function c() { console.log('c'); } function b() { console.log('b'); c(); console.trace(); } function a() { console.log('a'); b(); } a();
這時候咱們再觀察console
,就看不到c
方法了ide
VM61:8 console.trace b @ VM61:8 a @ VM61:13 (anonymous) @ VM61:16
由於console.trace()
的調用是發生在了c
調用以後,所以這個時候,棧頂c
的幀已經出棧,天然就看不到了。函數
一般程序有異常的時候都會有一個Error
對象拋出。Error.prototype
有如下幾種標準屬性:ui
constructor
this
message
prototype
name
更多的,能夠翻翻MDN的文檔。其中有一個stack
屬性,要重點關注。儘管這是一個非標準屬性,可是絕大多數瀏覽器都支持這個屬性。
通常咱們使用try/catch
來捕獲異常,同時,咱們還可使用finally
來作一些清理的工做,由於finally
裏的代碼是必定會執行的。
try { console.log('The try block is running...'); } finally { try { throw new Error('Error inside finally.'); } catch (err) { console.log('Caught an error inside the finally block.'); } }
有一個值得探討的地方,那就是,你能夠throw任何數據而不單單是一個Error類的實例。
function runWithoutThrowing(func) { try { func(); } catch (e) { console.log('There was an error, but I will not throw it.'); console.log('The error\'s message was: ' + e.message) } } function funcThatThrowsString() { throw 'I am a String.'; } runWithoutThrowing(funcThatThrowsString);
這種狀況下,e.message
的值必定就是undefined
了,由於你拋出的並非一個Error類的實例。
異常還能夠做爲第一個參數傳給callback函數。舉個fs.readdir
的例子,
const fs = require('fs'); fs.readdir('/example/i-do-not-exist', function callback(err, dirs) { if (err instanceof Error) { // `readdir` will throw an error because that directory does not exist // We will now be able to use the error object passed by it in our callback function console.log('Error Message: ' + err.message); console.log('See? We can use Errors without using try statements.'); } else { console.log(dirs); } });
思路就兩種:
Error.prototype.stack
Error.captureStackTrace是NodeJS提供的一個方法,這個方法會捕捉當前的調用堆棧,而後保存到你指定的對象。
const myObj = {}; function c() { } function b() { // Here we will store the current stack trace into myObj Error.captureStackTrace(myObj); c(); } function a() { b(); } // First we will call these functions a(); // Now let's see what is the stack trace stored into myObj.stack console.log(myObj.stack);
另一種就是利用Error對象的stack
屬性。但裏有個問題,就是你不知道在try/catch
裏拋出的是什麼樣的值,這個值它不必定是Error類的實例。不過咱們依然可以處理,並且是很是巧妙的進行處理。好比看看Chai這個斷言庫的AssertionError
類的構造函數。
// `ssfi` stands for "start stack function". It is the reference to the // starting point for removing irrelevant frames from the stack trace function AssertionError (message, _props, ssf) { var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') , props = extend(_props || {}); // Default values this.message = message || 'Unspecified AssertionError'; this.showDiff = false; // Copy from properties for (var key in props) { this[key] = props[key]; } // Here is what is relevant for us: // If a start stack function was provided we capture the current stack trace and pass // it to the `captureStackTrace` function so we can remove frames that come after it ssf = ssf || arguments.callee; if (ssf && Error.captureStackTrace) { Error.captureStackTrace(this, ssf); } else { // If no start stack function was provided we just use the original stack property try { throw new Error(); } catch(e) { this.stack = e.stack; } } }