[英]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()
已在上面进行了编辑。
变更摘要:
urlAr
切片 urlAr
给runnerMethod()来启动其8个并行批处理。 requestIndex
:以非常简单的方式恢复。 编辑版本的行为与问题中原始代码的行为相似但不相同。 区别在于每个批次都是预定义的 ,没有响应。
最终,如果此版本的内存消耗较少并且实际上运行完成,那么消除响应行为可能是值得付出的代价,这是练习的目的。
要查看未编辑的代码,请参阅问题的编辑历史记录
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.