承诺重试模式内存占用

时间:2018-06-13 13:13:02

标签: javascript promise

考虑一个函数来发出HTTP请求,直到成功为止:

function fetch_retry(url) {
    return fetch(url).catch(function(error) {
        return fetch_retry(url);
    });
}

随着故障次数的增加,内存消耗会增加吗?

我认为callstack高度为O(1),但我不知道是否保留了clojure上下文(随时间增长)。

编辑:

情况2

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应该在内存中,因为它将在以后使用。

情形3

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。

  • n = 1000,内存:4.34 MB
  • n = 10000,内存:8.95 MB

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);
  • n = 1000,内存:4.12 MB
  • n = 10000,内存:7.07 MB

所以堆栈是平的,但我们认为内存不干净?

2 个答案:

答案 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。只有外部承诺和当前待定的承诺留在那里。

  

随着故障次数的增加,内存消耗会增加吗?

没有