Heim  >  Artikel  >  Web-Frontend  >  Eine kurze Analyse der JavaScript-Fehlerbehandlung und Stack-Tracing

Eine kurze Analyse der JavaScript-Fehlerbehandlung und Stack-Tracing

php中世界最好的语言
php中世界最好的语言Original
2017-11-17 13:27:452069Durchsuche

Manchmal werden im Prozess unserer Code-Sammlung einige Details der Fehlerbehandlung und Stapelverfolgung ignoriert. Wenn Sie jedoch auf diese Details achten, sind sie beim Schreiben von Bibliotheken im Zusammenhang mit Tests oder Fehlerbehandlung sehr nützlich Diese Idee kann die Art und Weise, wie wir mit dem Stapel umgehen, erheblich verbessern. Wenn die Behauptung des Benutzers fehlschlägt, können wir dem Benutzer schnellere Informationen geben um nutzlose Daten zu löschen und sich nur auf nützliche Daten zu konzentrieren. Gleichzeitig kann Ihnen ein besseres Verständnis des Errors-Objekts und seiner zugehörigen Eigenschaften dabei helfen,

Fehler besser zu nutzen.

Wie das Der Aufrufstapel (der Funktion) funktioniert

Bevor wir über Fehler sprechen, müssen wir zunächst das Prinzip des Aufrufstapels (der Funktion) verstehen:

Wenn eine Funktion aufgerufen wird, wird sie an die Spitze verschoben Nachdem die Funktion abgeschlossen ist, wird sie von der Oberseite des Stapels entfernt.

Die Datenstruktur des Stapels ist rückwärts. First Out, bekannt als LIFO (Last In, First Out).

Zum Beispiel:

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


Im obigen Beispiel wird die Funktion a zur Laufzeit oben auf dem Stapel hinzugefügt. Dann, wenn Funktion b wird innerhalb von Funktion a aufgerufen, Funktion b wird an die Spitze des Stapels verschoben. Wenn Funktion c innerhalb von Funktion b aufgerufen wird, wird sie ebenfalls an die Spitze des Stapels verschoben Wenn Funktion c ausgeführt wird, enthält der Stapel a, b und c (in dieser Reihenfolge). Der Funktionsaufruf kehrt zu Funktion b zurück. Nachdem die Ausführung von Funktion b abgeschlossen ist, wird sie ebenfalls von der Oberseite des Stapels entfernt, und dann kehrt der Funktionsaufruf zu Funktion a zurück wird nach der Ausführung von der Oberseite des Stapels entfernt.

Um das Verhalten des Stapels in der Demo besser zu demonstrieren, können Sie die Konsolenverfolgung () verwenden, um die aktuellen Stapeldaten auf der Konsole auszugeben. Gleichzeitig müssen Sie die Ausgabestapeldaten der Reihe nach von oben nach unten lesen.

Das Ausführen des obigen Codes im REPL-Modus des Knotens führt zu der folgenden Ausgabe:

Wie Sie sehen können, enthält der Stapel bei der Ausgabe von Funktion c die Funktionen a, b und c.

Wenn Sie die aktuellen Stapeldaten in Funktion b danach ausgeben Funktion c ist abgeschlossen, Sie werden sehen, dass Funktion c von der Oberseite des Stapels entfernt wurde und der Stapel nur die Funktionen a und b enthält.
function c() {
    console.log('c');
    console.trace();
}
function b() {
    console.log('b');
    c();
}
function a() {
    console.log('a');
    b();
}
a();

Wie Sie sehen können, nachdem Funktion c abgeschlossen ist Beim Ausführen wurde es von der Oberseite des Stapels entfernt.
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)


Fehlerobjekt und Fehlerbehandlung

Wenn während der Ausführung des Programms ein Fehler auftritt, wird normalerweise ein Fehlerobjekt ausgelöst . Das Error-Objekt kann als vom benutzerdefinierten Fehlerobjekt geerbter Prototyp verwendet werden. Das

Error.prototype-Objekt enthält die folgenden Eigenschaften:

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();
}
constructor – zeigt auf den Konstruktor der Instanz

Nachricht – Fehlermeldung
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)

Name – Fehlername (Typ)

Die oben genannten sind die Standardeigenschaften von Error.prototype. Darüber hinaus haben verschiedene Betriebsumgebungen ihre eigenen Besonderheiten Eigenschaften. In Umgebungen wie Node, Firefox, Chrome, Edge, IE 10+, Opera und Safari 6+ verfügt das Error-Objekt über eine Stack-Eigenschaft, die den Stack-Trace des Fehlers enthält Fehlerinstanz Der Stack-Trace enthält alle Stack-Strukturen seit dem Konstruktor.

Wenn Sie mehr über die spezifischen Eigenschaften des Error-Objekts erfahren möchten, können Sie diesen Artikel auf MDN lesen.

In Um einen ausgelösten Fehler abzufangen, muss try...catch verwendet werden, um den Code einzuschließen, der den Fehler verursachen könnte. Der Parameter von Catch ist die Fehlerinstanz, die aufgetreten ist geworfen.

Wie Java erlaubt auch JavaScript die Verwendung des Schlüsselworts „final“ nach try/catch. Nach der Fehlerbehandlung können Sie im „final“-Anweisungsblock einige Aufräumarbeiten durchführen.

Syntaktisch können Sie den Try-Anweisungsblock und andere verwenden. Es muss kein Catch-Anweisungsblock folgen, sondern ein Final-Anweisungsblock. Das bedeutet, dass es drei verschiedene Try-Anweisungsformen gibt:

versuchen...fangen

versuchen...endlich

versuchen...fangen...endlich

Try-Anweisung kann auch Try-Anweisung einbetten:

Sie können Try-Anweisungen auch in Catch einbetten oder schließlich:

Das ist wichtig zu beachten Wenn ein Fehler ausgelöst wird, können Sie anstelle eines Fehlerobjekts einfach einen einfachen Wert auslösen. Dies sieht zwar cool aus und ist zulässig, ist jedoch kein empfohlener Ansatz, insbesondere für Entwickler von Bibliotheken und Frameworks, die mit dem Code anderer Leute umgehen müssen , weil es keinen Standard gibt, auf den man sich beziehen kann, und es keine Möglichkeit gibt, zu wissen, was von den Benutzern empfangen wird. Sie können dem Benutzer nicht vertrauen, dass er ein Fehlerobjekt auslöst, da er dies möglicherweise nicht tut und einfach eine Zeichenfolge ausgibt oder Wert. Dies bedeutet auch, dass es schwierig ist, mit Stapelinformationen und anderen Metainformationen umzugehen. Zum Beispiel:

Wenn der Parameter vom Benutzer an die Funktion runWithoutThrowing übergeben wird Wenn ein Fehlerobjekt ausgelöst wird, kann der obige Code den Fehler normal erfassen. Wenn dann eine Zeichenfolge ausgelöst wird, wird Folgendes angezeigt: Habe einige Fragen:

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中正则表达式的含义与使用

Das obige ist der detaillierte Inhalt vonEine kurze Analyse der JavaScript-Fehlerbehandlung und Stack-Tracing. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn