繁体   English   中英

JavaScript内存泄漏问题-Promise和递归

[英]Javascript memory leak issue - promises & recursion

我在这段代码中遇到内存问题:

var RequestManager = function(customRequestArgs){

    var requestManager = this;

    this.customRequestArgs = customRequestArgs              || [];



    this.CustomRequest = function(url, data){

        var requestDeferred = $.Deferred();

        // set default xmlRequestArgs
        var xmlRequestArgs = {
            method  : "GET",
            url     : url,
            onload  : function(response) {
                requestDeferred.resolve(response.responseText);
            },
            onerror : function(response){
                requestDeferred.reject('xmlRequest failed', response);
            }
        };
        // set custom xmlRequestArgs
        var i;
        for(i in requestManager.customRequestArgs){
            if(requestManager.customRequestArgs.hasOwnProperty(i)){
                xmlRequestArgs[i] = requestManager.customRequestArgs[i];
            }
        }

        // append data, depending on method
        var d = [];
        for(i in data){
            if(data.hasOwnProperty(i)){
                d.push(i+'='+encodeURIComponent(data[i]));
            }
        }
        var dataString = d.join('&');

        if(xmlRequestArgs.method.toLowerCase() === 'get'){
            if(url.indexOf('?')>=0){
                xmlRequestArgs.url = url+dataString;
            }
            else{
                xmlRequestArgs.url = url+'?'+dataString;
            }
        }
        if(xmlRequestArgs.method.toLowerCase() === 'post'){
            xmlRequestArgs.data = dataString;
        }


        // run request
        GM_xmlhttpRequest(xmlRequestArgs);

        return requestDeferred;
    };

    this.BatchRequestRunner = function(args){

        var maxParallelRequests = args.maxParallelRequests || 8;

        var onEachStart         = args.onEachStart              || function(requestIndex, url){return undefined;};          // must return undefined or loader promise (i.e. for cached results)
        var onEachSuccess       = args.onEachSuccess            || function(result, requestIndex, url){return result;};     // must return result or promise that resolves to result
        var onEachError         = args.onEachError              || function(error, requestIndex, url){return error;};       // must return error or promise that resolves to error

        var urlAr               = args.urlAr                    || [];

        var storeResults        = args.storeResults             || false;

        var reversedUrlArClone  = urlAr.slice(0).reverse();
        var deferredAr          = [];
        var resultAr            = [];
        var errorAr             = [];


        var runnerMethod = function(){

            if(reversedUrlArClone.length > 0){

                // get request url
                var url = reversedUrlArClone.pop();

                // get urlIndex (i-th url in urlAr)
                var requestIndex = urlAr.length - reversedUrlArClone.length - 1;


                // run onEachStart
                $.when(onEachStart(requestIndex, url)).then(function(loaderPromise){

                    if(loaderPromise === undefined){

                        // set loaderPromise
                        loaderPromise = requestManager.CustomRequest(url);

                    }

                    var generateOnSuccess = function(requestIndex){
                        return function(result){


                            $.when(onEachSuccess(result, requestIndex, url)).then(function(result){

                                // store result
                                if(storeResults){
                                    resultAr[requestIndex] = result;
                                }

                                // resolve deferredAr[requestIndex]
                                deferredAr[requestIndex].resolve();

                                // start runnerMethod for next request
                                runnerMethod();

                            });

                        };
                    };
                    var generateOnError = function(requestIndex){
                        return function(error){

                            $.when(onEachError(error, requestIndex, url)).then(function(error){

                                // store error
                                errorAr[requestIndex] = error;

                                // reject deferredAr[requestIndex]
                                deferredAr[requestIndex].reject();

                                // start runnerMethod for next request
                                runnerMethod(); 


                            });

                        };
                    };

                    // handle loader
                    loaderPromise.done(generateOnSuccess(requestIndex));
                    loaderPromise.fail(generateOnError(requestIndex));

                });

            }

        };

        var startParallelRequestThread = function(){
            runnerMethod();
        };

        var start = function(){
            var i,
                runnerDeferred  = $.Deferred();

            // setup deferredAr
            for(i=0;i<urlAr.length;i++){
                deferredAr.push($.Deferred());
            }

            // setup onSuccess
            $.when.apply($, deferredAr)
            .done(function(){
                runnerDeferred.resolve(resultAr);
            })
            // setup onError
            .fail(function(){
                runnerDeferred.reject(errorAr);
            });

            // start requestThreads
            for(i=0;i<maxParallelRequests;i++){
                startParallelRequestThread();
            }

            return runnerDeferred;
        };


        return {
            start       : start
        };

    };



    return {
        BatchRequestRunner  : this.BatchRequestRunner,
        CustomRequest       : this.CustomRequest,
    };
};

它应该是执行批处理请求的类。 用户可以设置默认请求参数(其他标头等)和大量批处理设置。

当代码按预期执行时,浏览器会在一段时间后崩溃。 检查任务管理器后,我看到选项卡的进程占用了越来越多的内存。 我一直在努力寻找原因,但一直未能做到。 有人有什么想法吗?

请让我知道是否可以清除任何内容。

问候,klmdb

好的,我想我会考虑代码,似乎您跳了很多不必要的圈。 主要可以通过使用两个标准技巧来大大简化代码:

  • 使用$.extend() (在两个地方)可以避免手动循环遍历对象。
  • 使用Array.prototype.reduce()代替“递归”将数组转换成.then()链。

以下版本的其他功能是:

  • 结果和错误通过承诺链传递,而不是在外部数组中累积。
  • requestIndex的需求(在许多地方)都消失了,对其显式关闭进行维护的需求也消失了。
  • 没有创建延迟的对象,这应该有助于减少可执行文件的内存消耗。
  • 现在,在调用RequestManager()new是可选的。 关于是否要使用new代码,原始代码不明确。

这是简化版...

var RequestManager = function(customRequestArgs) {
    var CustomRequest = function(url, data) {
        //GM_xmlhttpRequest is assumed to call $.ajax() (or one of its shorthand methods) and return a jqXHR object
        return GM_xmlhttpRequest($.extend({ //$.extend() replaces several lines of original code
            method: "GET",
            url: url,
            data: data
        }, customRequestArgs || {})).then(function(response) {
            return response.responseText;
        }, function(jqXHR, textStatus, errorThrown) {
            return ('xmlRequest failed: ' + textStatus);
        });
    };
    //Defaults are best defined (once per RequestManager) as an object, which can be extended with $.extend().
    var batchRequestDefaults = {
        maxParallelRequests: 8,
        onEachStart: function(url) { return undefined; }, // must return undefined or loader promise (i.e. for cached results)
        onEachSuccess: function(result, url){ return result; }, // must return result or promise that resolves to result
        onEachError: function(error, url){ return error; }, // must return error or promise that resolves to error.
        urlAr: [],
        storeResults: false
    };
    var BatchRequestRunner = function(args) {
        args = $.extend({}, batchRequestDefaults, args); //$.extend() replaces several lines of original code
        function runnerMethod(index, urlAr) {
            //Note recursion is avoided here by the use of .reduce() to build a flat .then() chain.
            return urlAr.reverse().reduce(function(promise, url) {
                var requestIndex = index++;
                return promise.then(function(result1) {
                    return $.when(args.onEachStart(requestIndex, url)).then(function(p) {
                        return (p === undefined) ? CustomRequest(url) : p;
                    }).then(function(result2) {
                        args.onEachSuccess(result2, requestIndex, url);
                        // No return value is necessary as result2 is assumed 
                        // to be fully handled by onEachSuccess(),
                        // so doesn't need to be passed down the promise chain.
                    }, function(error) {
                        // This is messy but : 
                        // (a) is consistent with the stated rules for writing onEachError() functions.
                        // (b) maintains the original code's behaviour of keeping going despite an error.
                        // This is achieved by returning a resolved promise from this error handler.
                        return $.when(args.onEachError(error, requestIndex, url)).then(function(error) {
                            return $.when(); //resolved promise
                        });
                    });
               });
            }, $.when());
        }
        var start = function() {
            // start requestThreads
            var i, promises = [],
                pitch = Math.ceil(args.urlAr / args.maxParallelRequests),
                startIndex, endIndex;
            for(i=0; i<args.maxParallelRequests; i++) {
                startIndex = pitch * i;
                endIndex = pitch * (i + 1) - 1;
                promises.push(runnerMethod(startIndex, args.urlAr.slice(startIndex, endIndex)));
            }
            // Note: Results and errors are assumed to be fully handled by onEachSuccess() and onEachError() so do not need to be handled here or passed on down the promise chain.
            return $.when.apply(null, promises);
        };
        return {
            start: start
        };
    };
    return {
        BatchRequestRunner: BatchRequestRunner,
        CustomRequest: CustomRequest
    };
};

未经测试,因此可能需要调试

到目前为止,最困难的方面是错误的处理。 在这方面,原始代码的行为颇为奇怪,我尝试通过使用虚假(不间断)错误来模仿。 凌乱但杂乱无章,但清除了递归后,我想不出另一种方法。

除了我自己的错误外,行为上的唯一区别应该在于start()返回的promise,它现在将提供结果数组和(伪)错误数组,并捆绑到js纯对象中。 尽管出现错误,这与runnerMethod继续运行是一致的。

现在,结果是通过承诺链传递的,“ storeResults”已经消失了。 除了storeResults === true之外,我看不到任何理由要运行。

我唯一的假设是$是jQuery,而GM_xmlhttpRequest使用jQuery.ajax()并返回(或可以使其返回)其jqXHR对象。 从我看来,这似乎是合理的。 如果该假设无效,那么您将需要还原该部分代码。

有关更多说明,请参见代码注释。

在调试时,如果仍然崩溃,那么我建议它只是内存不足,而不是泄漏本身

编辑

阅读(在下面的评论中)批处理过程和onEachError()等的描述后, start()runnerMethod()已在上面进行了编辑。

变更摘要:

  • 批处理定义:start()现在通过将urlAr 切片 urlAr给runnerMethod()来启动其8个并行批处理。
  • requestIndex :以非常简单的方式恢复。

编辑版本的行为与问题中原始代码的行为相似但不相同。 区别在于每个批次都是预定义的 ,没有响应。

最终,如果此版本的内存消耗较少并且实际上运行完成,那么消除响应行为可能是值得付出的代价,这是练习的目的。

要查看未编辑的代码,请参阅问题的编辑历史记录

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM