如何在一段时间后停止执行包含无限循环的Javascript函数

时间:2016-08-25 17:27:06

标签: javascript infinite-loop

假设我有一段包含无限循环的代码:

function infiniteLoop() {
    while(true) {
        //do something, eg.
        document.getElementById("someID").innerHTML = "Blah";
    }
}

如果我们在在线编译器中执行此代码,浏览器将崩溃。我想防止这种情况发生。所以我尝试按照this answer跟随代码:

function willNotCrash() {
    myVar = setInterval(infiniteLoop, 5000);
    setTimeout(function(){
        clearInterval(myVar);
    }, 4000);
}

此代码不会导致浏览器崩溃,因为我在infiniteLoop()调用clearInterval(myVar)之前停止执行。

我的问题是,如果他们在一段时间内没有响应(例如,在5秒之后或浏览器崩溃之前),我该如何停止执行这些功能。

例如,如果我们在https://www.compilejava.net/

中复制粘贴以下代码
public class HelloWorld {
    public static void main(String[] args) {
        while(true) {
            System.out.println("Blah");
        }
    }
}

我们得到了一个很好的输出,

  

脚本执行的时间超过5秒,因此被杀死了。

以下是我当前的代码:http://js.do/code/106546

3 个答案:

答案 0 :(得分:4)

这有点棘手但完全可行。您需要对脚本进行标记,然后重建它,但在每个循环和函数调用中插入一个计数器增量。如果计数器超过某个阈值,则弹出。我在这里做到了:https://littleminigames.com/

您可以在https://bitbucket.org/cskilbeck/littleminigames/src

看到来源

有趣的位在wrapper.js(https://bitbucket.org/cskilbeck/littleminigames/src/ac29d0d0787abe93c75b88520050a6792c04d34d/public_html/static/js/wrapper.js?at=master&fileviewer=file-view-default

Google escodegen,estraverse和esprima

我非常依赖这个:https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js

wrapper.js,按要求:

// Don't obfuscate this file! We depend on the toString() of functions!
// this was all nicked from https://github.com/CodeCosmos/codecosmos/blob/master/www/js/sandbox.js

(function(mainApp) {

    'use strict';
    var esprima = window.esprima,
        estraverse = window.estraverse,
        escodegen = window.escodegen,
        errors = [],
        eng,
        Syntax = estraverse.Syntax;

    // This implements the jankiest possible "source map", where we keep an array
    // of [generatedLine, knownSourceLine]. Seems to essentially work.
    function SourceNode(line, col, _sourceMap, generated) {
        this.line = line;
        this.col = col;
        this.generated = generated;
    }

    SourceNode.prototype.toStringWithSourceMap = function toStringWithSourceMap() {
        var code = [];
        var mapLines = {};
        var map = [];
        // assumes that wrapCode adds two lines
        var line = 3;
        var lastMapLine = null;

        function walk(node) {
            if (typeof(node) === "string") {
                if (node) {
                    code.push(node);
                    var matches = node.match(/\n/g);
                    if (matches !== null) {
                        line += matches.length;
                    }
                }
            } else if (node instanceof SourceNode) {
                if (node.line !== null) {
                    if (!mapLines[line]) {
                        map.push([line, node.line]);
                        mapLines[line] = node.line;
                    }
                }
                walk(node.generated);
            } else {
                node.forEach(walk);
            }
        }
        walk(this);
        return {
            code: code.join(''),
            map: map
        };
    };

    SourceNode.prototype.toString = function toString() {
        return this.toStringWithSourceMap().code;
    };

    // This is used by escodegen
    window.sourceMap = {
        SourceNode: SourceNode
    };

    // TODO (chs): add in all the things that need to be masked
    function runWrapper($userCode, __sys) {
        var clear = __sys.clear,
            setpixel = __sys.setpixel,
            rectangle = __sys.rectangle,
            box = __sys.box,
            line = __sys.line,
            getpixel = __sys.getpixel,
            getpixeli = __sys.getpixeli,
            keypress = __sys.keypress,
            keyrelease = __sys.keyrelease,
            keyheld = __sys.keyheld,
            reset = __sys.reset;
        __sys.userFunction = __sys.catchErrors($userCode);
    }

    function extractCode(fn) {
        var code = fn.toString();
        return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
    }

    function makeOneLine(code) {
        return code.replace(/(\/\/[^\n]+|\n\s|\r\n\s*)/g, '');
    }

    var runTemplate = makeOneLine(extractCode(runWrapper));

    function wrapCode(code, template, functionName, postCode) {
        // avoid interpretation of the replacement string by using a fun.
        // otherwise mo' $ mo problems.
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter
        return ("'use strict';" + template.replace(/\$userCode/, function() {
            return 'function ' + functionName + '() {\n' + code + postCode + '\n}';
        }));
    }

    var injectStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?');").body[0];
    var injectElseStatement = esprima.parse("if (++__sys.ctr >= __sys.maxctr) throw new Error('Script halted - infinite loop?'); else ;").body[0];

    function CallExpression(callee, args) {
        this.callee = callee;
        this.arguments = args;
    }
    CallExpression.prototype.type = Syntax.CallExpression;

    function Identifier(name) {
        this.name = name;
    }
    Identifier.prototype.type = Syntax.Identifier;

    function BlockStatement(body) {
        this.body = body;
    }
    BlockStatement.prototype.type = Syntax.BlockStatement;

    function ReturnStatement(argument) {
        this.argument = argument;
    }
    ReturnStatement.prototype.type = Syntax.ReturnStatement;

    function FunctionExpression(id, params, body) {
        this.id = id;
        this.params = params;
        this.body = body;
        this.defaults = [];
        this.expression = false;
        this.generator = false;
        this.rest = null;
    }
    FunctionExpression.prototype.type = Syntax.FunctionExpression;

    function wrapId(node, defaultName) {
        if (node.loc) {
            var id = (node.id || {
                name: null,
                loc: null
            });
            var loc = id.loc || node.loc;
            var name = id.name || defaultName;
            return new Identifier(name + '$' + loc.start.line);
        } else {
            return node.id;
        }
    }

    function instrumentAST(ast) {
        var identifierStack = [];

        function pushIdentifier(s) {
            identifierStack[identifierStack.length - 1].push(s);
        }

        function popIdentifierStack() {
            identifierStack.pop();
        }

        function pushIdentifierStack() {
            identifierStack.push([]);
        }

        function peekLastIdentifier() {
            var lastStackIdx = identifierStack.length - 1;
            if (lastStackIdx >= 0) {
                var stack = identifierStack[lastStackIdx];
                if (stack.length) {
                    return stack[stack.length - 1];
                }
            }
            return '';
        }
        pushIdentifierStack();
        return estraverse.replace(ast, {
            enter: function enterAST(node) {
                switch (node.type) {
                    case Syntax.VariableDeclarator:
                        if (node.id.type === Syntax.Identifier) {
                            pushIdentifier(node.id.name);
                        }
                        break;
                    case Syntax.MemberExpression:
                        if (node.object.type === Syntax.Identifier) {
                            var id = node.object.name;
                            if (node.property.type === Syntax.Identifier) {
                                id += '__dot__' + node.property.name;       // huh? why mangle these?
                                // console.log(id);
                            }
                            pushIdentifier(id);
                        } else if (node.property.type === Syntax.Identifier) {
                            pushIdentifier(node.property.name);
                        }
                        break;
                    case Syntax.FunctionDeclaration:
                        pushIdentifierStack();
                        break;
                    case Syntax.FunctionExpression:
                        pushIdentifierStack();
                        break;
                    default:
                        break;
                }
                return node;
            },
            leave: function leaveAST(node) {
                switch (node.type) {
                    case Syntax.DoWhileStatement:
                        break;
                    case Syntax.ForStatement:
                        break;
                    case Syntax.FunctionDeclaration:
                        break;
                    case Syntax.FunctionExpression:
                        break;
                    case Syntax.WhileStatement:
                        break;
                    default:
                        return estraverse.SKIP;
                }
                // modify the BlockStatement in-place to inject the instruction counter

                if(node.body.body === undefined) {
                    // they have used a non-block statement as the body of a function or loop construct

                    // not allowed for function declarations - should never get here
                    if(node.type === Syntax.FunctionDeclaration) {
                        errors.push({
                            message: "Missing {",
                            line: node.loc.start.line,
                            column: node.loc.start.column
                        });
                    }
                    else {
                        // otherwise insert the test
                        var newBody = angular.copy(injectElseStatement);
                        newBody.alternate = node.body;
                        node.body = newBody;
                    }
                    return estraverse.SKIP;
                }

                node.body.body.unshift(injectStatement);
                if (node.type === Syntax.FunctionExpression) {
                    popIdentifierStack();
                    // __catchErrors(node)
                    node.id = wrapId(node, peekLastIdentifier());
                    return new CallExpression(
                        new Identifier("__sys.catchErrors"), [node]);
                }
                if (node.type === Syntax.FunctionDeclaration) {
                    popIdentifierStack();
                    // modify the BlockStatement in-place to be
                    // return __catchErrors(function id() { body });
                    var funBody = node.body;
                    node.body = new BlockStatement([
                        new ReturnStatement(
                            new CallExpression(
                                new CallExpression(
                                    new Identifier("__sys.catchErrors"), [new FunctionExpression(
                                        wrapId(node, peekLastIdentifier()), [],
                                        funBody)]), []))
                    ]);
                }
                return node;
            }
        });
    }

    // mainApp.sandbox('var a = 1; function update(frame) { clear(0); }').code

    // give it the source code as a string
    mainApp.sandbox = function(code) {
        var rc = {};
        this.errors = [];
        try {
            this.ast = instrumentAST(esprima.parse(code, { range: true, loc: true }));
            this.map = escodegen.generate(this.ast, { sourceMap: true, sourceMapWithCode: true });
            this.code = wrapCode(this.map.code, runTemplate, '', ';\n__sys.updateFunction = (typeof update === "function") ? update : null;');
        }
        catch(e) {
            this.errors.push({
                message: e.description,
                line: e.lineNumber,
                column: e.column
            });
        }
        if(this.code) {
            this.code = "eng.clientFunction = function(__sys) {" + this.code + "};";
        }
    };

    mainApp.sandbox.prototype.searchMap = function(needle) {
        // binary search
        var lo = 0;
        var hi = this.map.map.length;
        var mid, here;
        while (true) {
            mid = lo + ((hi - lo) >> 1);
            here = this.map.map[mid];
            if (mid === lo || here[0] === needle) {
                return here[1];
            } else if (here[0] > needle) {
                hi = mid;
            } else {
                lo = mid;
            }
        }
    };

})(mainApp);

答案 1 :(得分:2)

通常所有JavaScript都在一个线程中运行,因此在循环运行时无法运行任何可能阻止循环的JavaScript。使用HTML5 web workers,您可以在单独的线程中运行无限循环,然后您可以终止它:

var myWorker = new Worker( '/infinite.js ');
setTimeout( function ( ) {
    myWorker.terminate( );
}, 5000 );

但是,您的网络工作者无法访问DOM,因此无限循环的内容需要与您在问题中的内容不同。

答案 2 :(得分:0)

我在Bergi的评论中找到了我想要的东西,

  

或者,在每个循环体中放置一个if (Date.now() > dateAtStartOfExecution+5000) return;

所以现在我的代码看起来像:

function infiniteLoop() {
    dateAtStartOfExecution = Date.now();
    while(true) {
        //do something
        document.getElementById("someID").innerHTML = "Blah";
        if (Date.now() > dateAtStartOfExecution+5000) {
            alert("Taking too much time. Killing.");
            return;
        }
    }
}

如果我在5秒后运行此代码,我将收到警报,执行将停止。试试这个: http://js.do/code/106565