最近在学习vuejs设计与实现的编译器
在代码里面有大量的while这样的状态机,一不小心就会出现死循环。这让我非常的忧虑。
能否可以捕获到【死循环】。在检测到有死循环的时候,主动中断程序,并报告当前的死循环的程序的函数的名字。
这里的捕获是在不影响原代码的情况下,可以监听到死循环,并主动中断,并暴露死循环的函数名字。
当时经过几次尝试,例如给程序再包裹一个函数或注入代码,我发现好像都有些不大如意的地方。
嗯,那如果抽离这个检测死循环的代码到独立函数,且让有while的程序自己调用,是否就可以一定层度的减少心智压力以及更好的debug呢?
首先,我们需要得到运行函数的名字,js并没有提供这样的功能,但是函数的arguments.callee却可以拿到整个函数内容的文本
- function a(){
- //dosome
- console.log(arguments.callee.toString())
- }
执行代码a()后打印出来的内容是
得到的就是字符串
'function a(){
//dosome
console.log(arguments.callee.toString())
}'
但是这有个弊端,如果代码是匿名函数,就拿不到函数名字,例如下面这样的代码,虽然复制给了变量a,但是函数本身是匿名函数
- var a=function (){
- //dosome
- console.log(arguments.callee.toString())
- }
因为函数是个匿名函数,得到的字符串如下,这种情况就需要用户自己传递过来了
'function(){
//dosome
console.log(arguments.callee.toString())
}'
那我们可以使用正则拿到函数名字
- let str = arguments.callee.toString();
-
- var re = /^function\s*(\w+)\(/ig;
-
- var matches = re.exec(str);
-
- let fnName = matches[1];
如果在n秒内不断的执行某个函数那就可以认为他是死循环
我们可以用两个时间的间隔来判断这个函数执行了多久,如果大于某个时间,就让他break;
那确定了方案后开始,对这个抽离的函数进行分解
- var deadLoopCatch = function(argument, runFnName, deadLoopTimeLimit = 5000) {
- let fnName;
- if (runFnName) {
- fnName = runFnName;
- } else {
- let str = argument.callee.toString();
- var re = /^function\s*(\w+)\(/ig;
- var matches = re.exec(str);
- if (matches && matches[1]) {
- fnName = matches[1];
- } else {
- //如果是匿名函数,且用户没有传递第二个参数的时候,警告
- console.warn("When the loop is an anonymous function, please pass the second parameter for function deadLoopCatch")
- fnName = "---UMKNOW--";
- }
-
-
- }
- //初始化开始时间和结束时间
- let startTime = endTime = new Date().getTime();
- let result = {
- fnName,
- _startTime: startTime,
- _endTime: endTime,
- _deadLoopTimeLimit: deadLoopTimeLimit,
- updateTime: () => {
- result._endTime = new Date().getTime();
-
- },
- isDeadLoop: () => {
- let isDeadLoop = result._endTime - result._startTime >= result._deadLoopTimeLimit;
- //这代码好像有一点弊端,如果这里不执行的话,还是会死循环
- console.log("deadLoopCatch:result._endTime - result._startTime", result._endTime - result._startTime)
- if (isDeadLoop) {
- console.error(`Function ${fnName} triggered an endless loop!`)
- }
- return isDeadLoop;
-
- }
- }
- return result;
- }
一般情况下while不会执行耗时操作,所以如果一个while执行超过5秒,我们认为他就是死循环了,当然这个时间可以根据自己的实际情况调整
- var abc = function(a, b, c) {
- var i = 1;
- let deadLoopCatchInit = deadLoopCatch(arguments, 'abc', 1000);
- while (true) {
- deadLoopCatchInit.updateTime()
- if (deadLoopCatchInit.isDeadLoop()) {
- console.warn("强制中断", deadLoopCatchInit)
- break;
- }
- console.log("执行中....")
- }
- }
- abc()
另外个简单debug方式,就是在while第一行使用console.log(“函数名字”) 这种方式也可以定位到死循环是哪一个代码,但是却无法避免死循环。
- <!DOCTYPE html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Document</title>
- </head>
-
- <body>
- <p>为什么去掉地46行,死循环拦截就没效果了!!<span id="txt"></span></p>
- <script>
- var deadLoopCatch = function(argument, runFnName, deadLoopTimeLimit = 5000) {
- let fnName;
- if (runFnName) {
- fnName = runFnName;
- } else {
- let str = argument.callee.toString();
- var re = /^function\s*(\w+)\(/ig;
- var matches = re.exec(str);
- if (matches && matches[1]) {
- fnName = matches[1];
- } else {
- //如果是匿名函数,且用户没有传递第二个参数的时候,警告
- console.warn("When the loop is an anonymous function, please pass the second parameter for function deadLoopCatch")
- fnName = "---UMKNOW--";
- }
-
-
- }
- //初始化开始时间和结束时间
- let startTime = endTime = new Date().getTime();
- let result = {
- fnName,
- _startTime: startTime,
- _endTime: endTime,
- _deadLoopTimeLimit: deadLoopTimeLimit,
- updateTime: () => {
- result._endTime = new Date().getTime();
-
- },
- isDeadLoop: () => {
- let isDeadLoop = result._endTime - result._startTime >= result._deadLoopTimeLimit;
- //这代码好像有一点弊端,如果这里不执行的话,还是会死循环
- // console.log("deadLoopCatch:result._endTime - result._startTime", result._endTime - result._startTime)
- if (isDeadLoop) {
- console.error(`Function ${fnName} triggered an endless loop!`)
- }
- return isDeadLoop;
-
- }
- }
- return result;
- }
- window.onload = function() {
- const dom = document.querySelector("#txt")
- var abc = function(a, b, c) {
- var i = 1;
- let deadLoopCatchInit = deadLoopCatch(arguments, 'abc', 1000);
- while (true) {
- deadLoopCatchInit.updateTime()
- if (deadLoopCatchInit.isDeadLoop()) {
- console.warn("强制中断", deadLoopCatchInit)
- dom.innerHTML = `更新第(${i}次)后<b style='background:red;'>强制中断</b>了`
- break;
- }
- i++;
- dom.innerHTML = `更新第(${i}次)`
- console.log("执行中....")
- }
- }
- abc()
- }
- </script>
- </body>
-
- </html>
运行一下上面的代码后会得到个奇怪的线下console.log的运行会滞后,while已经结束了,html已经输出了“强制中断”,console打印还在不断的追赶
console延后执行了
貌似console是个微任务的赶脚,这一定程度上也说的通,因为本来是用来调试的,不堵塞住流程代码也是应该的。