Maison  >  Article  >  interface Web  >  Une brève analyse de la gestion des erreurs JavaScript et du suivi de la pile

Une brève analyse de la gestion des erreurs JavaScript et du suivi de la pile

php中世界最好的语言
php中世界最好的语言original
2017-11-17 13:27:452068parcourir

Parfois, dans le processus d'accumulation de notre code, certains détails de gestion des erreurs et de traces de pile sont ignorés, mais si vous faites attention à ces détails, ils sont très utiles pour écrire des bibliothèques liées aux tests ou à la gestion des erreurs. vous une excellente idée de traitement. Cette idée peut grandement améliorer la façon dont nous gérons la pile. Lorsque l'assertion de l'utilisateur échoue, nous donnerons plus d'informations rapides (pour aider l'utilisateur à localiser

Gérer correctement les informations de la pile vous permet). pour effacer les données inutiles et vous concentrer uniquement sur les données utiles. Dans le même temps, une meilleure compréhension de l'objet Erreurs et de ses propriétés associées peut vous aider à utiliser davantage les

Erreurs.

Comment le La pile d'appels (de fonction) fonctionne

Avant de parler d'erreurs, il faut d'abord comprendre le principe de la pile d'appels (de fonction) :

Lorsqu'une fonction est appelée, elle est poussée en haut de la pile. Une fois la fonction terminée, elle sera supprimée du haut de la pile

La structure des données de la pile est en arrière Premier sorti, connu sous le nom de LIFO (dernier entré, premier sorti).

Par exemple :

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


Dans l'exemple ci-dessus, lorsque la fonction a Au moment de l'exécution, elle sera ajoutée en haut de la pile. La fonction b est appelée à l'intérieur de la fonction a, la fonction b sera poussée vers le haut de la pile. Lorsque la fonction c est appelée à l'intérieur de la fonction b, lorsqu'elle est appelée en interne, elle sera également poussée vers le haut de la pile. Lorsque la fonction c s'exécute, la pile contient a, b et c (dans cet ordre).

Lorsque la fonction c s'exécute Une fois l'exécution de c terminée, elle sera supprimée du haut de la pile, puis le flux de contrôle. de l'appel de fonction revient à la fonction b. Une fois l'exécution de la fonction b terminée, elle sera également supprimée du haut de la pile, puis l'appel de fonction

Le flux de contrôle revient à la fonction a. sera supprimé du haut de la pile après l'exécution.

Afin de mieux démontrer le comportement de la pile dans la démo, vous pouvez utiliser la console trace() pour afficher les données actuelles de la pile sur la console. Dans le même temps, vous devez lire les données de la pile de sortie dans l'ordre de haut en bas.

L'exécution du code ci-dessus en mode REPL de Node entraînera la sortie suivante :

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

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)
Comme vous pouvez le voir, lors de la sortie de la fonction c, la pile contient les fonctions a, b et c.


Si vous sortez les données actuelles de la pile dans la fonction b après la fonction c est terminée, vous verrez que la fonction c a été supprimée du haut de la pile et la pile ne comprend que les fonctions a et b.

Comme vous pouvez le voir, une fois la fonction c terminée , il a été supprimé du haut de la pile.

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();
}
Objet d'erreur et gestion des erreurs

Lorsqu'une erreur se produit pendant l'exécution du programme When , un objet Error est généralement généré. L'objet Error peut être utilisé comme prototype hérité par l'objet error défini par l'utilisateur. L'objet
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.prototype contient les propriétés suivantes :

constructor–pointe vers le constructeur de l'instance.

message–message d'erreur

nom–nom de l'erreur (type)

Ce qui précède sont les propriétés standard de Error.prototype De plus, différents environnements d'exploitation ont leurs propres spécificités. propriétés. Dans des environnements tels que Node, Firefox, Chrome, Edge, IE 10+, Opera et Safari 6+

, l'objet Error a une propriété de pile, qui contient la trace de pile de l'erreur. instance d'erreur La trace de pile contient toutes les structures de pile depuis le constructeur.

Si vous souhaitez en savoir plus sur les propriétés spécifiques de l'objet Error, vous pouvez lire cet article sur MDN.

Pour to throw Pour une erreur, le mot-clé throw doit être utilisé. Afin d'intercepter une erreur générée, try...catch doit être utilisé pour contenir le code qui peut provoquer l'erreur. Le paramètre de Catch est l'instance d'erreur qui a été générée. .

Comme Java, JavaScript permet également l'utilisation du mot-clé enfin après try/catch. Après avoir traité l'erreur, vous pouvez effectuer un travail de nettoyage dans le bloc d'instructions final.

Syntaxiquement. , vous pouvez utiliser le bloc d'instructions try et d'autres. Il n'est pas nécessaire qu'il soit suivi d'un bloc d'instructions catch, mais il doit être suivi d'un bloc d'instructions enfin. Cela signifie qu'il existe trois formes d'instructions try différentes :

essayer...attraper

essayer...enfin

essayer...attraper...enfin

Vous pouvez également intégrer des instructions try dans l'instruction Try :


Vous pouvez également intégrer des instructions try dans catch ou enfin :

Il est important de notez que lorsqu'une erreur est générée, vous pouvez simplement lancer une valeur simple au lieu d'un objet Error. Bien que cela ait l'air sympa et soit autorisé, ce n'est pas une approche recommandée, en particulier pour les développeurs de bibliothèques et de frameworks qui doivent gérer d'autres. le code des gens, car il n'y a pas de norme à laquelle se référer et il n'y a aucun moyen de savoir ce qui sera reçu des utilisateurs. Quoi. Vous ne pouvez pas faire confiance à l'utilisateur pour lancer un objet Error, car il ne peut pas le faire et simplement le lancer. une chaîne ou une valeur Cela signifie également qu'il est difficile de gérer les informations de pile et autres méta-informations
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;);
}

Par exemple :

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

Si le paramètre est transmis par l'utilisateur au. La fonction runWithoutThrowing renvoie un objet d'erreur, le code ci-dessus peut capturer l'erreur normalement. Ensuite, si une chaîne est lancée, elle frappera. Vous avez quelques questions :

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn