翻譯-JavaScript異常和調用堆棧的處理

原文

JavaScript Errors and Stack Traces in Depthhtml

調用棧Call Stack是如何工做的

棧是一個後進先出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對象拋出。Error.prototype有如下幾種標準屬性:ui

  • constructorthis

  • messageprototype

  • 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);
    }
});

處理調用堆棧

思路就兩種:

  1. Error.captureStackTrace(NodeJS)

  2. 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;
    }
  }
}

參考文檔

  1. arguments.callee

  2. arguments.caller

  3. Error

相關文章
相關標籤/搜索