我在这段代码中遇到了内存问题:
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,
};
};
它应该是一个执行批量请求的类。用户可以设置默认请求参数(附加 header 等)和一堆批处理设置。
虽然代码按预期执行,但浏览器在一段时间后崩溃。检查任务管理器显示选项卡的进程占用了越来越多的内存。 我一直试图寻找其中的原因,但一直未能找到。请问有人有什么想法吗?
如果我可以澄清任何问题,请告诉我。
问候, klmdb
最佳答案
好吧,我想我已经仔细考虑了代码,而且看起来您跳过了一些不必要的麻烦。主要通过使用两个标准技巧可以大大简化代码:
- 使用
$.extend()
(在两个地方),避免了手动循环对象的需要。 - 使用
Array.prototype.reduce()
将数组转换为 .then() 链来代替“递归”。
以下版本的其他功能包括:
- 结果和错误通过 Promise 链传递,而不是累积在外部数组中。
- 对
requestIndex
的需求(在很多地方)消失了,维护它的显式闭包的需求也消失了。 - 不会创建延迟对象,这应该有助于减少可执行文件对内存的占用。 现在,在调用
new
是可选的。原始代码对于new
是否有意为之并不明确。
RequestManager()
时,这是简化版本...
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
尽管出现错误仍继续运行是一致的。
现在结果是通过 promise 链传递的,“storeResults”已经消失了。我看不出有任何理由想要使用 storeResults === true
之外的任何东西来运行。
我唯一的(?)假设是 $
是 jQuery 并且 GM_xmlhttpRequest
使用 jQuery.ajax()
并返回(或者可以是返回)其jqXHR
对象。从我看来,这似乎是合理的。如果假设无效,那么您将需要恢复该代码部分。
有关进一步的说明,请参阅代码中的注释。
调试时,如果它仍然崩溃,那么我建议它只是内存不足,而不是泄漏本身。
编辑
阅读(在下面的评论中)批处理和 onEachError()
等的描述后,start()
和 runnerMethod()
已经上面已经编辑过。
变更摘要:
- 批处理定义:start() 现在通过将
urlAr
的切片传递给 runnerMethod() 来启动 8 个并行批处理。 requestIndex
:以非常简单的方式恢复。
编辑版本的行为与问题中的原始代码类似但不完全相同。不同之处在于每个批处理都是预定义的,而不是响应式的。
最终,如果此版本内存消耗较少并且实际上运行完成(这就是练习的目标),则删除响应行为可能是值得付出的代价。
要查看未编辑的代码,请查看问题的编辑历史记录
关于Javascript 内存泄漏问题 - promise 和递归,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25996400/