资讯专栏INFORMATION COLUMN

翻译-JavaScript异常和调用堆栈的处理

adam1q84 / 1984人阅读

摘要:调用堆栈实际上就是一个方法列表,按调用顺序保存所有在运行期被调用的方法。调用堆栈会将当前正在执行的函数调用压入堆栈,一旦函数调用结束,又会将它移出堆栈。

原文

JavaScript Errors and Stack Traces in Depth

调用栈Call Stack是如何工作的

栈是一个后进先出LIFO (Last in,First out)的数据结构。调用堆栈实际上就是一个方法列表,按调用顺序保存所有在运行期被调用的方法。调用堆栈会将当前正在执行的函数调用压入堆栈,一旦函数调用结束,又会将它移出堆栈。

console.trace()编写一个简单的例子来演示一下

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方法了

VM61:8 console.trace
b @ VM61:8
a @ VM61:13
(anonymous) @ VM61:16

因为console.trace()的调用是发生在了c调用之后,因此这个时候,栈顶c的帧已经出栈,自然就看不到了。

Error对象以及异常处理

通常程序有异常的时候都会有一个Error对象抛出。Error.prototype有以下几种标准属性:

constructor

message

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.captureStackTrace(NodeJS)

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;
    }
  }
}
参考文档

arguments.callee

arguments.caller

Error

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89863.html

相关文章

  • JavaScript 编程精解 中文第三版 八、Bug 错误

    摘要:幸运的是,使用符号创建的构造器,如果在不使用来调用,则始终会报错,即使在非严格模式下也不会产生问题。 来源:ApacheCN『JavaScript 编程精解 中文第三版』翻译项目原文:Bugs and Errors 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 部分参考了《JavaScript 编程精解(第 2 版)》 调试的难度是开始编写代码的两倍。 因此,如...

    wujl596 评论0 收藏0
  • JavaScript如何工作:引擎,运行时调用堆栈概述

    摘要:如果我们进入一个函数,我们在堆栈的顶部。看看下面的代码当引擎开始执行此代码时,调用堆栈将为空。之后,步骤如下调用堆栈中的每个条目称为堆栈帧。这正是抛出异常时构造堆栈跟踪的方式当异常发生时,它基本上是调用堆栈的状态。 随着JavaScript越来越受欢迎,团队正在利用这个技术栈在多个层次- 前端,后端,混合应用程序,嵌入式设备等等提供支持。 这篇文章旨在成为系列中第一个旨在深入挖掘Jav...

    wwolf 评论0 收藏0
  • javasctipt 工作原理之调用

    摘要:译者注翻译一个对新手比较友好的工作原理解析系列文章注意以下全部是概念经验丰富的老鸟可以离场啦正文从这里开始随着的流行团队们正在利用来支持多个级别的技术栈包括前端后端混合开发嵌入式设备以及更多这篇文章旨在成为深入挖掘和实际上他是怎么工作的系列 译者注 翻译一个对新手比较友好的 JavaScript 工作原理解析系列文章 注意: 以下全部是概念,经验丰富的老鸟可以离场啦 正文从这里开始 随...

    Pines_Cheng 评论0 收藏0
  • 翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道JS》

    摘要:每个函数调用都将开辟出一小块称为堆栈帧的内存。当第二个函数开始执行,堆栈帧增加到个。当这个函数调用结束后,它的帧会从堆栈中退出。保持堆栈帧跟踪函数调用的状态,并将其分派给下一个递归调用迭。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM...

    LeviDing 评论0 收藏0
  • JavaScript中错误正确处理方式,你用对了吗?

    摘要:单元测试会体现出以上错误处理程序的作用如果出现问题,错误处理程序就会返回。同时错误会展开堆栈,这对调试非常有帮助。展开堆栈处理异常的一种方式是在调用堆栈的顶部加入。确保你的错误处理处在相同域中,这样会保留原始消息,堆栈和自定义错误对象。 JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化。如果将浏览器设想为JavaScript的事件驱动...

    chaos_G 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<