澳门新浦京娱乐游戏深入理解 JavaScript 错误和堆栈追踪

有的时候大家并不关切那么些细节,但那方面包车型客车知识料定有用,特别是当您正在编写与测量检验或errors相关的库。比如那些星期大家的chai中现身了三个令人作呕的Pull
Request,它大大改过了小编们管理旅社跟踪的不二等秘书技,并在顾客断言失利时提供了越来越多的音讯。

有的时候候大家会忽略错误管理和货栈跟踪的局地细节,
可是那几个细节对于写与测量检验或错误管理相关的库来讲是非常实用的. 例如下周,
对于 Chai 就有多少个要命棒的PGL450, 该PPAJERO相当的大地改良了我们管理仓库的办法,
当顾客的预见失败的时候, 大家会授予更加的多的提示音讯(扶植客户张开一定卡塔尔.

操作客栈记录可以令你清理无用数据,并三月不知肉味管理主要事项。此外,当你实在弄清楚Error及其特性,你将会更有信念地动用它。

创制地拍卖仓库音讯能使您拨冗无用的数量, 而只注意于有用的数据. 同时,
当越来越好地领会 Errors 对象及其有关属性之后,
能有利于你更丰硕地利用 Errors.

正文在这里以前部分可能太过分轻易,但当你起来拍卖宾馆记录时,它将变得某个有些复杂,所以请确定保障您在初叶那个这部分章节以前曾经充足知晓前面包车型客车源委。

(函数的卡塔尔(قطر‎调用栈是怎么专门的学问的

在斟酌错误早前, 先要理解下(函数的卡塔尔国调用栈的规律:

当有贰个函数被调用的时候, 它就被压入到宾馆的最上端, 该函数运营成功之后,
又会从饭馆的顶上部分被移除.

库房的数据结构正是后进先出, 以 LIFO (last in, first out卡塔尔(قطر‎ 著称.

例如:

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

在上述的亲自过问中, 当函数 a 运转时, 其会被增添到货仓的顶上部分. 然后,
当函数 b 在函数 a 的此中被调用时, 函数 b 会被压入到库房的最上部.
当函数 c 在函数 b 的里边被调用时也会被压入到库房的顶端.

当函数 c 运维时, 货仓中就含有了 ab 和 c(按此顺序卡塔尔国.

当函数 c 运转实现之后, 就能从旅馆的顶上部分被移除,
然后函数调用的调控流就重返函数 b. 函数 b 运行完之后,
也会从货仓的顶端被移除, 然后函数调用的调节流就重回函数 a. 最后,
函数 a 启动成功之后也会从仓库的最上端被移除.

为了越来越好地在demo中示范仓库的行事,
能够行使 console.trace() 在支配台出口当前的库房数据. 同期,
你要以从上至下的顺序阅读输出的酒馆数据.

function c() {
    console.log('c');
    console.trace();
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

在 Node 的 REPL 格局中运营上述代码会拿走如下输出:

Trace
    at c (repl:3:9)
    at b (repl:3:1)
    at a (repl:3:1)
    at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node's internals
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)

正如所看见的, 当从函数 c 中输出时, 仓库中包涵了函数 ab 以及c.

只要在函数 c 运转成功之后, 在函数 b 中输出当前的库房数据,
就能够看见函数 c 已经从旅馆的最上部被移除,
那时候货仓中仅满含函数 a 和 b.

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
    console.trace();
}

function a() {
    console.log('a');
    b();
}

正如所观察的, 函数 c 运营成功以往, 已经从酒馆的顶上部分被移除.

Trace
    at b (repl:4:9)
    at a (repl:3:1)
    at repl:1:1  // <-- For now feel free to ignore anything below this point, these are Node's internals
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:513:10)

库房调用咋做事

在批评errors以前大家一定要领会货仓调用哪些做事。它特别轻巧,但对此大家将在深刻的源委来说却是至关心注重要的。假诺您曾经清楚那有些内容,请任何时候跳过本节。

每当函数被调用,它都会被推到饭店的最上部。函数推行达成,便会从仓库顶端移除。

这种数据构造的风趣之处在于倒数入栈的将会首先个从货仓中移除,那也正是大家所耳濡目染的LIFO(后进,先出State of Qatar天性。

那也等于说我们在函数x中调用函数y,那么相应的酒馆中的顺序为x y

设若你有下边那样的代码:

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

在下面这里例子中,当实施a函数时,a便会加多到仓库的顶上部分,然后当b函数在a函数中被调用,b也会被加多到仓库的顶上部分,依次类推,在b中调用c也会发生雷同的事情。

c实行时,仓库中的函数的逐个为a b c

c施行完成后便会从栈顶移除,当时调节流重新赶回了b中,b施行实现相似也会从栈顶移除,最后调整流又回去了a中,最后a施行实现,a也从客栈中移除。

大家能够运用console.trace()来更加好的示范这种表现,它会在调整台打字与印刷出脚下货仓中的记录。其余,平时来讲你应当从上到下读取旅舍记录。用脑筋想上边包车型地铁每一行代码都是在哪调用的。

function c() {
    console.log('c');
    console.trace();
}

function b() {
    console.log('b');
    c();
}

function a() {
    console.log('a');
    b();
}

a();

在Node REPL服务器上运转上述代码会获得如下结果:

Trace
    at c (repl:3:9)
    at b (repl:3:1)
    at a (repl:3:1)
    at repl:1:1 // <-- For now feel free to ignore anything below this point, these are Node's internals
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)

如您所见,当大家在c中打印旅舍,仓库中的记录为a,b,c

比如大家几天前在b中而且在c履行完之后打字与印刷货仓,我们将会意识c现已从货仓的顶上部分移除,只剩余了ab

function c() {
    console.log('c');
}

function b() {
    console.log('b');
    c();
    console.trace();
}

function a() {
    console.log('a');
    b();
}

a();

正如你看看的那么,客栈中已经未有c,因为它已经到位运营,已经被弹出去了。

Trace
    at b (repl:4:9)
    at a (repl:3:1)
    at repl:1:1  // <-- For now feel free to ignore anything below this point, these are Node's internals
    at realRunInThisContextScript (vm.js:22:35)
    at sigintHandlersWrap (vm.js:98:12)
    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
    at REPLServer.defaultEval (repl.js:313:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.onLine (repl.js:513:10)

总结:调用方法,方法便会增多到仓库最上部,实行实现之后,它就能够从旅馆中弹出。

Error对象和错误管理

当程序运营现身错误时,
平日会抛出二个 Error 对象. Error 对象能够作为客商自定义错误对象世襲的原型.

Error.prototype 对象包蕴如下属性:

  • constructor–指向实例的构造函数
  • message–错误音讯
  • name–错误的名字(类型State of Qatar

上述是 Error.prototype 的专门的学业属性, 别的,
不相同的运市场价格况都有其特定的属性. 在诸如 Node, Firefox, Chrome, Edge, IE
10+, Opera 以致 Safari 6+ 那样的意况中, Error 对象具备 stack 属性,
该属性满含了不当的货仓轨迹.
叁个荒谬实例的旅舍轨迹满含了自布局函数之后的有着仓库布局.

假设想询问越来越多关于 Error 对象的特定属性, 能够阅读 MDN
上的那篇作品.

为了抛出四个谬误, 必需接受 throw 关键字. 为了 catch 二个抛出的不当,
必得接受 try...catch 包罗大概跑出荒唐的代码.
Catch的参数是被跑出的大谬不然实例.

如 Java 同样, JavaScript 也同意在 try/catch 之后选择 finally 关键字.
在拍卖完错误之后, 能够在 finally语句块作一些杀绝职业.

在语法上, 你能够应用 try 语句块而其后不要跟着 catch 语句块,
但必需随着 finally 语句块. 这意味有三种差异的 try 语句情势:

  • try...catch
  • try...finally
  • try...catch...finally

Try语句内还能在嵌入 try 语句:

try {
    try {
        throw new Error('Nested error.'); // The error thrown here will be caught by its own `catch` clause
    } catch (nestedErr) {
        console.log('Nested catch'); // This runs
    }
} catch (err) {
    console.log('This will not run.');
}

也得以在 catch 或 finally 中嵌入 try 语句:

try {
    throw new Error('First error');
} catch (err) {
    console.log('First catch running');
    try {
        throw new Error('Second error');
    } catch (nestedErr) {
        console.log('Second catch running.');
    }
}

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.');
    }
}

亟需入眼说雀巢下的是在抛出荒诞时,
能够只抛出贰个简练值实际不是 Error 对象. 即使那看起来看酷何况是允许的,
但那并不是叁个引进的做法,
特别是对此一些亟需管理别人代码的库和框架的开辟者, 因为未有职业能够参照,
也无从得悉会从顾客这里拿走哪些. 你不能够相信客户会抛出 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 funcThatThrowsError() {
    throw new TypeError('I am a TypeError.');
}

runWithoutThrowing(funcThatThrowsError);

若果顾客传递给函数 runWithoutThrowing 的参数抛出了二个荒唐对象,
下边的代码能健康捕获错误. 然后, 假使是抛出三个字符串,
就能够遇上一些标题了:

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

这段日子第三个 console.log 会输出undefined. 那看起来不是很要紧,
但假让你供给保障 Error 对象有贰个特定的性质也许用另一种办法来处理 Error 对象的特定属性(比如 Chai的throws断言的做法卡塔尔,
你就得做多量的行事来保障程序的不利运转.

与此同一时候, 假若抛出的不是 Error 对象, 也就取得不到 stack 属性.

Errors 也得以被看成别的对象, 你也无须抛出它们,
这也是干什么当先四分之一次调函数把 Errors 作为第三个参数的原因. 举个例子:

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 对象也足以用于 rejected promise, 那使得相当轻松管理 rejected
promise:

new Promise(function(resolve, reject) {
    reject(new Error('The promise was rejected.'));
}).then(function() {
    console.log('I am an error.');
}).catch(function(err) {
    if (err instanceof Error) {
        console.log('The promise was rejected with an error.');
        console.log('Error Message: ' + err.message);
    }
});

Error对象 和 Error处理

当程序发生错误时,平时都会抛出三个Error对象。Error对象也足以看成一个原型,顾客能够扩张它并制造自定义错误。

Error.prototype目的平日常有以下属性:

  • constructor– 实例原型的构造函数。
  • message – 错误信息
  • name – 错误名称

上述都以行业内部属性,(但)一时候各类碰着都有其一定的脾性,在譬喻Node,Firefox,Chorme,Edge,IE
10+,Opera 和 Safari 6+
中,还应该有一个包含错误仓库记录的stack品质。错误仓库记录富含从(宾馆尾部)它和睦的布局函数到(旅舍顶上部分)全数的货仓帧。

举个例子想询问越多关于Error对象的切切实实性质,作者刚烈推荐MDN上的那篇小说。

抛出错误必需采纳throw最主要字,你不得不将可能抛出荒唐的代码包裹在try代码块内并紧跟着三个catch代码块来捕获抛出的大谬不然。

正如Java中的错误管理,try/catch代码块后紧跟着四个finally代码块在JavaScript中也是同等允许的,不论try代码块内是还是不是抛出非凡,finally代码块内的代码都会进行。在成就管理现在,最好实践是在finally代码块中做一些清理的事体,(因为State of Qatar无论你的操作是不是见到成效,都不会影响到它的进行。

(鉴于State of Qatar下面所聊起的保有业务对大多数人来说都以芝麻小事,那么就让大家来谈一些不为人所知的内部原因。

try代码块前边不必紧跟着catch,但(此种意况下卡塔尔其后必需紧跟着finally。那意味大家能够应用三种分化样式的try语句:

  • try...catch
  • try...finally
  • try...catch...finally

Try语句能够像下面那样相互嵌套:

try {
    try {
        throw new Error('Nested error.'); // The error thrown here will be caught by its own `catch` clause
    } catch (nestedErr) {
        console.log('Nested catch'); // This runs
    }
} catch (err) {
    console.log('This will not run.');
}

您以至还足以在catchfinally代码块中嵌套try语句:

try {
    throw new Error('First error');
} catch (err) {
    console.log('First catch running');
    try {
        throw new Error('Second error');
    } catch (nestedErr) {
        console.log('Second catch running.');
    }
}

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.');
    }
}

还会有非常重大的一点值得注意,那便是大家居然能够完全没供给抛出Error指标。尽管那看起来极其cool且十二分自由,但实质上并非那样,越发是对开发第三方库的开采者来讲,因为他俩必得管理客商(使用库的开荒者卡塔尔国的代码。由于贫乏正规,他们并不可能把控客商的作为。你不能够相信客商并简短的抛出多个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 funcThatThrowsError() {
    throw new TypeError('I am a TypeError.');
}

runWithoutThrowing(funcThatThrowsError);

要是你的客户像下面那样传递一个抛出Error指标的函数给runWithoutThrowing函数(那就身当其境了卡塔尔(قطر‎,不过总某个人偷想懒直接抛出二个String,那您就麻烦了:

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

以往第4个console.log会打印出 the error’s message
is undefined.这么看来也没多大的事(后果卡塔尔呀,可是只要你要求确定保证有些属性存在于Error对象上,或以另一种办法(举个例子Chai的throws断言 does))处理Error对象的特定属性,那么您做须要更加多的行事,以担保它会不荒谬薪给。

除此以外,当抛出的值不是Error对象时,你不大概访问其他首要数据,举例stack,在少数景况中它是Error目的的三天性能。

Errors也得以像此外任何对象相符采取,并不一定非得要抛出她们,那也是它们为啥屡屡被用作回调函数的率先个参数(俗称
err first卡塔尔。 在底下的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);
    }
});

说起底,在rejecting
promises时也能够动用Error指标。那使得它更便于管理promise rejections:

new Promise(function(resolve, reject) {
    reject(new Error('The promise was rejected.'));
}).then(function() {
    console.log('I am an error.');
}).catch(function(err) {
    if (err instanceof Error) {
        console.log('The promise was rejected with an error.');
        console.log('Error Message: ' + err.message);
    }
});

拍卖仓库

这一节是针对帮助 Error.captureStackTrace的运维条件,
比如Nodejs.

Error.captureStackTrace 的第二个参数是 object,
第二个可选参数是二个 functionError.captureStackTrace 会捕获仓库音讯,
并在第一个参数中创建 stack 属性来积存捕获到的库房新闻.
要是提供了第二个参数, 该函数将作为仓库调用的终点. 因而,
捕获到的仓库音信将只突显该函数调用在此以前的新闻.

用上面包车型客车四个demo来讲多美滋(Dumex卡塔尔(قطر‎(DumexState of Qatar下. 先是个,
仅将捕获到的商旅消息存于叁个普通的目的之中:

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

// This will print the following stack to the console:
//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//    at a (repl:2:1)
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)

从上面包车型地铁示范能够看出, 首先调用函数 a(被压入货仓卡塔尔(قطر‎,
然后在 a 里面调用函数 b(被压入饭店且在a之上),
然后在 b 中抓获到当下的库房音讯, 并将其积累到 myObj 中. 所以,
在调控台出口的商旅音信中仅富含了 a和 b 的调用音信.

现在, 我们给 Error.captureStackTrace 传递叁个函数作为第三个参数,
看下输出音讯:

const myObj = {};

function d() {
    // Here we will store the current stack trace into myObj
    // This time we will hide all the frames after `b` and `b` itself
    Error.captureStackTrace(myObj, b);
}

function c() {
    d();
}

function b() {
    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);

// This will print the following stack to the console:
//    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)
//    at emitOne (events.js:101:20)

当将函数 b 作为第1个参数字传送给 Error.captureStackTraceFunction 时,
输出的客栈就只满含了函数 b 调用在此之前的新闻(即使 Error.captureStackTraceFunction 是在函数 d 中调用的State of Qatar,
那也正是怎么只在调控台出口了 a.
那样管理方式的裨益正是用来掩藏一些与客户毫无干系的在那之中落到实处细节.

垄断酒店追踪

上面啰嗦了那么多,压轴的着器重来了,那正是怎么着支配货仓追踪。

本章特地针对那几个像NodeJS支Error.captureStackTrace的环境。

Error.captureStackTrace函数选用三个object用作第4个参数,第叁个参数是可选的,选择三个函数。capture
stack trace
捕获当前仓库追踪,并在对象对象中开创三个stack属性来囤积它。借使提供了第三个参数,则传递的函数将被视为调用仓库的极端,由此货仓跟踪将仅显示调用该函数在此之前发生的调用。

让我们用例子来证实那或多或少。首先,我们将捕获当前饭店追踪并将其累积在公共对象中。

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

// This will print the following stack to the console:
//    at b (repl:3:7) <-- Since it was called inside B, the B call is the last entry in the stack
//    at a (repl:2:1)
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)

不知道你放在心上到没,我们先是调用了a(a入栈卡塔尔国,然后大家a中又调用了b(b入栈且在a之上)。然后在b中大家捕获了现阶段旅舍记录并将其积存在myObj中。因而在控制嘉义才会遵照b a的逐一打字与印刷仓库。

不久前让大家给Error.captureStackTrace传递一个函数作为第三个参数,看看会发生哪些:

const myObj = {};

function d() {
    // Here we will store the current stack trace into myObj
    // This time we will hide all the frames after `b` and `b` itself
    Error.captureStackTrace(myObj, b);
}

function c() {
    d();
}

function b() {
    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);

// This will print the following stack to the console:
//    at a (repl:2:1) <-- As you can see here we only get frames before `b` was called
//    at repl:1:1 <-- Node internals below this line
//    at realRunInThisContextScript (vm.js:22:35)
//    at sigintHandlersWrap (vm.js:98:12)
//    at ContextifyScript.Script.runInThisContext (vm.js:24:12)
//    at REPLServer.defaultEval (repl.js:313:29)
//    at bound (domain.js:280:14)
//    at REPLServer.runBound [as eval] (domain.js:293:12)
//    at REPLServer.onLine (repl.js:513:10)
//    at emitOne (events.js:101:20)

当把b传给Error.captureStackTraceFunction时,它掩没了b自家以至它现在有所的调用帧。由此调节台仅仅打印出两个a

现今你应该会问本身:“那毕竟有啥样用?”。那十一分有用,因为你能够用它来掩藏与顾客毫无干系的中间落到实处细节。在Chai中,我们运用它来防止向顾客显示我们是什么样进行检查和断言本人的不相干的内部情状。

参考

JavaScript Errors and Stack Traces in
Depth

操作仓库追踪实战

正如本身在上一节中关系的,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;
    }
  }
}

如您所见,大家选择Error.captureStackTrace破获货仓追踪并将它存款和储蓄在大家正在创设的AssertError实例中(借使存在的话),然后大家将叁个伊始仓库函数传递给它,以便从货仓追踪中剔除不相干的调用帧,它只浮现Chai的中间贯彻细节,最后使旅馆变得清晰明了。

当今让大家来探视@meeber在这个无不侧目的P智跑中提交的代码。

在您最早看上边包车型客车代码此前,笔者必需告诉您addChainableMethod主意是干啥的。它将传递给它的链式方法增加到断言上,它也用带有断言的主意标识断言本人,并将其保存在变量ssfi(运营商旅函数指示符卡塔尔(قطر‎中。那也就表示当前断言将会是酒店中的最后二个调用帧,因而大家不会在库房中浮现Chai中的任何进一层的内部方法。我从没加多任何代码,因为它做了累累政工,有一点困难,但假诺你想读它,点本人阅读。

上边包车型客车那些代码片段中,大家有二个lengOf预见的逻辑,它检查三个指标是或不是有自然的length。大家期望客商可以像这么来利用它:expect(['foo', 'bar']).to.have.lengthOf(2)

function assertLength (n, msg) {
    if (msg) flag(this, 'message', msg);
    var obj = flag(this, 'object')
        , ssfi = flag(this, 'ssfi');

    // Pay close attention to this line
    new Assertion(obj, msg, ssfi, true).to.have.property('length');
    var len = obj.length;

    // This line is also relevant
    this.assert(
            len == n
        , 'expected #{this} to have a length of #{exp} but got #{act}'
        , 'expected #{this} to not have a length of #{act}'
        , n
        , len
    );
}

Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain);

在上边的代码片段中,作者鼓起强调了与大家今后连带的代码。让大家从调用this.assert起来讲起。

以下是this.assert办法的源代码:

Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) {
    var ok = util.test(this, arguments);
    if (false !== showDiff) showDiff = true;
    if (undefined === expected && undefined === _actual) showDiff = false;
    if (true !== config.showDiff) showDiff = false;

    if (!ok) {
        msg = util.getMessage(this, arguments);
        var actual = util.getActual(this, arguments);

        // This is the relevant line for us
        throw new AssertionError(msg, {
                actual: actual
            , expected: expected
            , showDiff: showDiff
        }, (config.includeStack) ? this.assert : flag(this, 'ssfi'));
    }
};

assert艺术担负检查断言布尔表达式是不是由此。假使不通过,大家则实例化三个AssertionError。不驾驭您放在心上到没,在实例化AssertionError时,大家也给它传递了两个储藏室追踪函数提示器(ssfi卡塔尔,假诺布置的includeStack居于开启状态,我们透过将this.assert自个儿传递给它来为客户展现任何货仓追踪。反之,大家则只展现ssfi标识中存款和储蓄的剧情,掩瞒掉仓库追踪中更加多的内部落到实处细节。

今日让大家来谈谈下一行和大家连带的代码吧:

`new Assertion(obj, msg, ssfi, true).to.have.property('length');`

As you can see here we are passing the content we’ve got from
the ssfi flag when creating our nested assertion. This means that when
the new assertion gets created it will use this function as the starting
point for removing unuseful frames from the stack trace. By the way,
this is the Assertion constructor:
如您所见,大家在创立嵌套断言时将从ssfi标识中的内容传递给了它。那象征新创造的断言会使用非常情势作为开局调用帧,从而能够从宾馆追踪中解除没有的调用栈。顺便也看下Assertion的布局器吧:

function Assertion (obj, msg, ssfi, lockSsfi) {
    // This is the line that matters to us
    flag(this, 'ssfi', ssfi || Assertion);
    flag(this, 'lockSsfi', lockSsfi);
    flag(this, 'object', obj);
    flag(this, 'message', msg);

    return util.proxify(this);
}

不晓得您是或不是还记的本身以前说过的addChainableMethod办法,它接受自身的父级方法设置ssfi注脚,那代表它一直处于货仓的最底层,我们得以去除它之上的持有调用帧。

通过将ssfi传送给嵌套断言,它只检查大家的指标是或不是富有长度属性,大家就能够防止重新苏醒设置我们将要用作初始目标器的调用帧,然后在旅社中能够见见早先的addChainableMethod

那只怕看起来有些复杂,所以让大家回看一下大家想从栈中删除无用的调用帧时Chai中所发生的政工:

  1. 当大家运维断言时,大家将它谐和的点子作为移除仓库中的下多个调用帧的参阅

  1. 预感战败时,大家会移除全数我们在参考帧之后保存的里边调用帧。

  1. 如若存在嵌套的断言。大家亟须依旧使用当前断言的父方法作为剔除下八个调用帧的参照他事他说加以考查点,由此我们把当下的ssfi(起初函数提醒器)传递给大家所创设的断言,以便它能够保存。

若果您想越来越尖锐的问询它,
小编也刚烈推荐你读书@米贝的评论和介绍

保持联系

倘诺您有其余疑问,主张照旧不承认本身写的其它内容,你都足以在上边包车型大巴评价中享受您的主张,或然在twitter卡塔尔上和本人交流。假若自身犯了不当,小编很情愿听到你要说的话,并做出别的修改。

感激阅读!

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图