考虑一个函数来发出HTTP请求,直到成功为止:
function fetch_retry(url) {
return fetch(url).catch(function(error) {
return fetch_retry(url);
});
}
随着故障次数的增加,内存消耗会增加吗?
我认为callstack高度为O(1),但我不知道是否保留了clojure上下文(随时间增长)。
function fetch_retry(url) {
return fetch(url).catch(function(error) {
//return fetch_retry(url)+1;
return fetch_retry(url).then(res=>{res.body+='a'});
});
}
(假设fetch解析为数字) 常量1应该在内存中,因为它将在以后使用。
function fetch_retry(url, odd_even) {
return fetch(url).catch(function(error) {
//return fetch_retry(url, 1-odd_even)+odd_even;
return fetch_retry(url, 1-odd_even).then(res=>{res.body+=str(odd_even)});
});
}
fetch_retry('http://..', 0)
通过交替0和1,编译器不能重用操作数。
没有人解释case2和3,所以我进行了实验。 令人惊讶的是,在所有情况下,内存都会增加。
function f(n) {
return new Promise(function (resolve, reject) {
gc();
if(n==10000) return resolve(0);
// else return f(n+1).then(value=>value+n);
// else return f(n+1).then(value=>value);
else return resolve(f(n+1));
});
}
f(0).then(res => {
console.log('result: ', res);
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`memory: ${Math.round(used * 100) / 100} MB`);
}).finally(()=>console.log('finished'));
用
运行node --stack_size=999999 --expose_gc test.js
请注意,我每次都运行gc()以防止延迟GC。
9000个堆栈为5MB =每个呼叫590个字节。
一种可能性是保留每个堆栈中的resolve
函数。要删除它,
function f(n) {
return new Promise(function (resolve, reject) {
gc();
if(n==10000) resolve(0);
// else return f(n+1).then(value=>value+n);
else return f(n+1).then(value=>value);
// else return f(n+1);
const used = process.memoryUsage().heapUsed / 1024 / 1024;
console.log(`memory: ${Math.round(used * 100) / 100} MB`);
});
}
f(0);
所以堆栈是平的,但我们认为内存不干净?
答案 0 :(得分:1)
随着故障次数的增加,内存消耗会增加吗?
它没有。 Promises的现代实现没有记忆它们来自的承诺。我可以验证这是Firefox,Chrome,Safari和Edge的情况。
'旧'实施有这个问题(想想Q)。现代浏览器和快速库如bluebird'解析'解析后的引用。
那就是说,现在没有理由以递归方式实现,你可以这样做:
async function fetch_retry(url) {
while(true) { // or limit retries somehow
try {
await fetch(url);
} catch { }
}
}
请注意,永远重试是一个错误的想法,我热烈建议对后端更友好,并在进行重试和限制重试次数时使用指数退避。
实际上很难衡量这一点,因为调试器连接到V8(Chrome的JavaScript引擎)时会收集异步堆栈跟踪,并且在递归示例中,内存将增长。
您可以关闭Chrome devtools的异步堆栈跟踪(有一个复选框)。
这会在开发中泄漏一点(堆栈帧)但不会在生产中泄漏。
如果fetch
本身在错误上泄漏了内存(有时会对某些类型的请求执行此操作),那么代码显然会泄漏。据我所知,所有常绿浏览器都修复了漏洞,但未来可能会有漏洞。
答案 1 :(得分:0)
return fetch(url).catch(function(error) {
return fetch_retry(url);
// Function exits, losing reference to closure
// Closure gets garabge collected soon
});
当回调(catch
)被调用时,它将松开对catched函数的引用,因此函数关闭将收集carbage。只有外部承诺和当前待定的承诺留在那里。
随着故障次数的增加,内存消耗会增加吗?
没有