首頁 web前端 js教程 JavaScript 錯誤處理與堆疊追蹤淺析

JavaScript 錯誤處理與堆疊追蹤淺析

Nov 17, 2017 pm 01:27 PM
javascript js 追蹤

有时候在我们的代码累积过程中,会忽略错误处理和堆栈追踪的一些细节,但是如果你注意这些细节的话,它们对于写与测试或错误处理相关的库来说是非常有用的,现在就给大家一个非常棒的处理思路,这个思路能极大的改善了我们处理堆栈的方式, 当用户的断言失败的时候, 我们会给予更多的提示信息(帮助用户进行定位).

合理地处理堆栈信息能使你清除无用的数据, 而只专注于有用的数据. 同时, 当更好地理解 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 运行时, 堆栈中就包含了 a, b 和 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&#39;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.

如果在函数 c 运行完成之后, 在函数 b 中输出当前的堆栈数据, 就会看到函数 c 已经从堆栈的顶部被移除, 此时堆栈中仅包括函数 a 和 b.

function c() {
    console.log(&#39;c&#39;);
}
function b() {
    console.log(&#39;b&#39;);
    c();
    console.trace();
}
function a() {
    console.log(&#39;a&#39;);
    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&#39;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–错误的名字(类型)

上述是 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(&#39;Nested error.&#39;); // The error thrown here will be caught by its own `catch` clause
    } catch (nestedErr) {
        console.log(&#39;Nested catch&#39;); // This runs
    }
} catch (err) {
    console.log(&#39;This will not run.&#39;);
}
登入後複製

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

try {
    console.log(&#39;The try block is running...&#39;);
} finally {
    try {
        throw new Error(&#39;Error inside finally.&#39;);
    } catch (err) {
        console.log(&#39;Caught an error inside the finally block.&#39;);
    }
}
登入後複製

需要重点说明一下的是在抛出错误时, 可以只抛出一个简单值而不是 Error 对象. 尽管这看起来看酷并且是允许的, 但这并不是一个推荐的做法, 尤其是对于一些需要处理他人代码的库和框架的开发者, 因为没有标准可以参考, 也无法得知会从用户那里得到什么. 你不能信任用户会抛出 Error 对象, 因为他们可能不会这么做, 而是简单的抛出一个字符串或者数值. 这也意味着很难去处理堆栈信息和其它元信息.

例如:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log(&#39;There was an error, but I will not throw it.&#39;);
        console.log(&#39;The error\&#39;s message was: &#39; + e.message)
    }
}
function funcThatThrowsError() {
    throw new TypeError(&#39;I am a TypeError.&#39;);
}
runWithoutThrowing(funcThatThrowsError);
登入後複製

如果用户传递给函数 runWithoutThrowing 的参数抛出了一个错误对象, 上面的代码能正常捕获错误. 然后, 如果是抛出一个字符串, 就会碰到一些问题了:

function runWithoutThrowing(func) {
    try {
        func();
    } catch (e) {
        console.log(&#39;There was an error, but I will not throw it.&#39;);
        console.log(&#39;The error\&#39;s message was: &#39; + e.message)
    }
}
function funcThatThrowsString() {
    throw &#39;I am a String.&#39;;
}
runWithoutThrowing(funcThatThrowsString);
登入後複製

现在第二个 console.log 会输出undefined. 这看起来不是很重要, 但如果你需要确保 Error 对象有一个特定的属性或者用另一种方式来处理 Error 对象的特定属性(例如 Chai的throws断言的做法), 你就得做大量的工作来确保程序的正确运行.同时, 如果抛出的不是 Error 对象, 也就获取不到 stack 属性.

Errors 也可以被作为其它对象, 你也不必抛出它们, 这也是为什么大多数回调函数把 Errors 作为第一个参数的原因. 例如:

const fs = require(&#39;fs&#39;);
fs.readdir(&#39;/example/i-do-not-exist&#39;, 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(&#39;Error Message: &#39; + err.message);
        console.log(&#39;See? We can use Errors without using try statements.&#39;);
    } else {
        console.log(dirs);
    }
});
登入後複製

最后, Error 对象也可以用于 rejected promise, 这使得很容易处理 rejected promise:

new Promise(function(resolve, reject) {
    reject(new Error(&#39;The promise was rejected.&#39;));
}).then(function() {
    console.log(&#39;I am an error.&#39;);
}).catch(function(err) {
    if (err instanceof Error) {
        console.log(&#39;The promise was rejected with an error.&#39;);
        console.log(&#39;Error Message: &#39; + err.message);
    }
});
登入後複製

处理堆栈

这一节是针对支持 Error.captureStackTrace的运行环境, 例如Nodejs.

Error.captureStackTrace 的第一个参数是 object, 第二个可选参数是一个 function.Error.captureStackTrace 会捕获堆栈信息, 并在第一个参数中创建

stack 属性来存储捕获到的堆栈信息. 如果提供了第二个参数, 该函数将作为堆栈调用的终点. 因此, 捕获到的堆栈信息将只显示该函数调用之前的信息.

用下面的两个demo来解释一下. 第一个, 仅将捕获到的堆栈信息存于一个普通的对象之中:

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&#39;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&#39;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 调用之前的信息(尽管 Error.captureStackTraceFunction 是在函数 d 中调用的), 这也就是为什么只在控制台输出了 a. 这样处理方式的好处就是用来隐藏一些与用户无关的内部实现细节.

感谢大家阅读本篇文章,之后也会给大家带来关于JS,关于前端的一些小技巧,希望大家共同探讨,一起进步。


相关推荐:

JavaScript和ECMAScript 之间的区别

JavaScript刷新页面location.reload()的用法

JavaScript中正则表达式的含义与使用

以上是JavaScript 錯誤處理與堆疊追蹤淺析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 Dec 17, 2023 pm 06:55 PM

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟,需要具體程式碼範例隨著網路和科技的快速發展,股票交易已成為許多投資者的重要途徑之一。而股票分析是投資人決策的重要一環,其中蠟燭圖被廣泛應用於技術分析。學習如何使用PHP和JS繪製蠟燭圖將為投資者提供更多直觀的信息,幫助他們更好地做出決策。蠟燭圖是一種以蠟燭形狀來展示股票價格的技術圖表。它展示了股票價格的

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

蘋果手機丟了關機怎麼追蹤精確位置 蘋果手機丟了關機怎麼追蹤精確位置 Mar 08, 2024 pm 02:30 PM

蘋果手機丟了關機想要找回也是可以的,方法也很簡單的,用戶可以選擇登入iCloud官網進行查找,或是同樣使用蘋果手機的朋友,使用他的手機來查找你的iPhone。蘋果手機丟了關機怎麼追蹤精確位置答:iCloud官網查找或是藉用他人iPhone設備查找1、用戶發現自己的蘋果手機丟了或不見了,即使是關機狀態也可以找到。 2.用戶直接登入iCloud官網,點選尋找我的iPhone手機,注意一定要輸入正確的帳號。 3.確保自己的帳號適合遺失的手機的帳號一致,這樣才有機會找回手機。 4.如果手機開機並連接

PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 Dec 18, 2023 pm 03:39 PM

隨著網路金融的快速發展,股票投資已經成為了越來越多人的選擇。而在股票交易中,蠟燭圖是常用的技術分析方法,它能夠顯示股票價格的變動趨勢,幫助投資人做出更精準的決策。本文將透過介紹PHP和JS的開發技巧,帶領讀者了解如何繪製股票蠟燭圖,並提供具體的程式碼範例。一、了解股票蠟燭圖在介紹如何繪製股票蠟燭圖之前,我們首先需要先了解什麼是蠟燭圖。蠟燭圖是由日本人

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

See all articles