我对这段代码存在内存问题:
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
答案 0 :(得分:1)
好吧,我想我已经深入思考了代码,看来你跳过了许多不必要的箍。主要通过使用两个标准技巧来大大简化代码:
$.extend()
(在两个地方),避免了手动循环对象的需要。Array.prototype.reduce()
将数组转换为.then()链代替&#34;递归&#34;。以下版本的其他功能包括:
requestIndex
(在许多地方)的需求消失了,需要明确关闭其维护。new
时,RequestManager()
现在可选。关于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()
返回的承诺,它现在将提供结果数组和(虚假)错误数组,捆绑到js普通对象中。这与runnerMethod
保持一致,尽管有错误。
现在,结果是通过承诺链传递的,&#39; storeResults&#39;已经消失了。我看不出有任何理由想要使用storeResults === true
以外的任何其他内容。
我的唯一(?)假设是$
是jQuery,GM_xmlhttpRequest
使用jQuery.ajax()
并返回(或可以返回)其jqXHR
对象。从我所看到的情况看,这似乎是合理的。如果假设无效,那么您将需要还原代码的该部分。
有关详细说明,请参阅代码内注释。
调试时,如果它仍然崩溃,那么我建议它只是内存饥饿而不是泄漏本身。
修改强>
阅读(以下评论中)批处理流程的说明和onEachError()
等,start()
和runnerMethod()
已在上面进行了编辑。
变更摘要:
urlAr
的 slices 传递给runnerMethod()来启动其8个并行批处理。 requestIndex
:以非常简单的方式恢复。编辑版本的行为与问题中原始代码的行为类似但不相同。不同之处在于每个批次都是预定义的,而不是响应。
最终,删除响应行为可能是一个值得付出的代价,如果这个版本的内存不足并实际运行完成,这是练习的目标。
要查看未经编辑的代码,请参阅问题的编辑记录