V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lqzhgood
V2EX  ›  Node.js

请教一个 Promise 递归的最佳实践(内存释放)

  •  
  •   lqzhgood · 2019-10-12 10:27:55 +08:00 · 6763 次点击
    这是一个创建于 1651 天前的主题,其中的信息可能已经有所发展或是发生改变。

    先上代码

    let loading = false;
    (async () => {
        if (loading) return;
        loading = true;
        await getAll();
        loading = false;
    })()
    
    function getAll(page = 1) {
        return new Promise(async (resolve, reject) => {
            try {
                const body = await getPage(page);
                bigHandle(body); //body 很大 处理完需要及时释放掉
                //  body = null; <--- 尝试过这个 没有用
                if (page < 10) {
                    await getAll(++page)
                }
                resolve();
            } catch (error) {
                reject(error);
            }
        })
    }
    
    

    这段代码由于 Promise 嵌套,上一个在等下一个 Promise 完成,上一个无法被释放,最初的 Promise 需要等到 page=10 的时候洋葱模型式的层层返回后释放,pm2 中看到内存一直在飙升。。

    如果去掉 Promise,改成 异步回调的形式 一切正常,但是 loading 状态的改变就要写到回调里面去,不是很直观 这里是简化的代码,真实业务中还有一大堆状态 不想都丢到函数的回调去处理 太不优雅了。 请问在使用 Promise 的时候 这种情况的最佳实现是什么?

    // node 节点 夜间模式阅读会更舒服 日间模式 太黑了。。

    第 1 条附言  ·  2019-10-12 11:20:04 +08:00
    page < 10 只是为了精简代码写的。 实际上这是一个状态值
    bigHandle(body) 就是在处理并提取这个状态值 来决定是不是要下一次 getAll。

    getAll 本来只是一个 getPage handle,只需要处理一个页面,后来发现还要处理后续页面,为了复用 bigHandle 逻辑自然就写成递归了~

    最初的需求已经变了 应该从头审视代码逻辑 而不该顺着坑往下填的。
    这里 GetAll 改成 while 死循环 里面 await 判断后 break,就行了。

    谢谢各位~ 3Q
    20 条回复    2019-10-12 14:58:10 +08:00
    lllllliu
        1
    lllllliu  
       2019-10-12 10:34:26 +08:00
    为什么不直接 Promise.all 展开呢。并没有看到或者说明下一次 Promise 需要前面做什么。如果需要前者可以链式调用呀,可以及时释放。
    lqzhgood
        2
    lqzhgood  
    OP
       2019-10-12 10:36:56 +08:00
    @lllllliu 因为第二页需要第一页的数据,所以需要递归调用。 而且需要控制频率,所以不能 Promise.all 并发一把梭
    ahsjs
        3
    ahsjs  
       2019-10-12 10:42:56 +08:00
    把 body 变量放外面?
    mcfog
        4
    mcfog  
       2019-10-12 10:43:53 +08:00 via Android
    promise 没学明白就拿 async await 来写代码就这样了

    用 new promise 来包别的 promise 已经是反模式了,还再加上 async await

    这代码没治了,从头重写吧
    ayase252
        5
    ayase252  
       2019-10-12 10:44:17 +08:00
    Promise 和 async 混用感觉很奇怪....确实按#1 来说,body 在处理 getAll(page)时是不必要的。
    ```
    getPage(page)
    .then((body) => {
    bigHandle(body)
    })
    .then(() => {
    getAll(++page)
    }, (error) => {
    //...
    })
    .
    ```
    yixiang
        6
    yixiang  
       2019-10-12 10:44:22 +08:00   ❤️ 1
    写 node 从不关心内存占用……感觉是伪命题。

    但你这个可能可以这么解决。

    ```
    var body;
    for (var i = 1; i < 10; i ++) {
    body = await getPage(page);
    bigHandle(body);
    }
    ```

    为啥想不开要递归……
    lqzhgood
        7
    lqzhgood  
    OP
       2019-10-12 10:47:05 +08:00
    @ahsjs 感觉不是单纯的 body 问题。body 再怎么大也就 1m 的纯文本。内存几十兆几十兆的涨。
    主要是 Promise 整个堆栈没有释放,这个才是内存爆炸的主要原因。
    但是 递归的 Promise 怎么合理的释放上一个 Promise 感觉这是个悖论了……
    所以来问问有没有这类问题的最佳实践。

    难道只能回到 回调地狱 来处理了么~
    jifengg
        8
    jifengg  
       2019-10-12 10:47:31 +08:00
    同意 @mcfog 说的。
    getAll 完全可以不用 Promise,也不要用递归。里面写个 for 循环就好了。有了 await /async,完全可以把 node 异步当成同步来开发。
    ahsjs
        9
    ahsjs  
       2019-10-12 10:51:24 +08:00
    let loading = false;
    (async () => {
    if (loading) return;
    loading = true;
    for (let i = 0; i < 10; i++) {
    let body = await getPage(page);
    bigHandle(body); //body 很大 处理完需要及时释放掉
    }
    loading = false;
    })()
    lllllliu
        10
    lllllliu  
       2019-10-12 10:52:14 +08:00
    emmm,Page 数量是提前可以知道的么? 提前的话只需要顺序处理就可以了啊。还可以加 Delay 随意。Reduce 或者直接 for await 不行么 哈哈哈。
    knva
        11
    knva  
       2019-10-12 11:05:32 +08:00
    要么 Promise 要么 async/await 混用头一次见
    sevenzhou1218
        12
    sevenzhou1218  
       2019-10-12 11:20:14 +08:00
    async 返回本身就是 promise 啊,总觉得代码怪怪的。
    muru
        13
    muru  
       2019-10-12 11:57:45 +08:00
    有流程控制的时候可以试试 promise chain
    [ ...Array(10).keys()].map(page => getPage(page)).reduce((pc, func) => {
    return pc.then(() => new Promise(resolve => func(resolve)), Promise.resolve());
    });
    jsq2627
        14
    jsq2627  
       2019-10-12 13:47:42 +08:00
    https://asciinema.org/a/dKWCuCHxZ3vkxOaifb5Rlksxj
    抛点砖。body = null 是有用的,能让内存使用减少一半,但是还是非常占用。手动触发 GC 能让内存占用维持在稳定水平
    miloooz
        15
    miloooz  
       2019-10-12 13:55:24 +08:00
    let loading = false;

    while(page<10){
    if (loading) return;

    loading = true;

    await getAll();
    loading = false;
    }
    withoutxx
        16
    withoutxx  
       2019-10-12 14:24:28 +08:00
    有老哥指点一下 Promise 和 async/await 怎么一起使用吗, 上面的回复让我看懵了
    yuankui
        17
    yuankui  
       2019-10-12 14:33:08 +08:00
    为啥不用循环,要用地递归来写一个自己若干天之后都不能理解的代码
    FrankHB
        18
    FrankHB  
       2019-10-12 14:50:06 +08:00
    在一个没法 reify 活动记录确保显式释放又没 proper tail call 保证的玩意儿里瞎搞?想多了。
    呵、呵:
    https://github.com/nodejs/CTC/issues/3
    有点意义的例子:
    https://srfi.schemers.org/srfi-45/srfi-45.html
    islxyqwe
        19
    islxyqwe  
       2019-10-12 14:51:38 +08:00
    怎么又是 new promise 又是 async 的, async 就是返回 promise 的语法啊
    递归的话肯定要尾递归的,我觉得这么写就行了
    let loading = false;
    (async () => {
    if (loading) return;
    loading = true;
    await getAll();
    loading = false;
    })()

    async function getAll(page = 1) {
    const body = await getPage(page);
    bigHandle(body); //body 很大 处理完需要及时释放掉
    if (page < 10) {
    return getAll(++page)
    }
    }
    IamUNICODE
        20
    IamUNICODE  
       2019-10-12 14:58:10 +08:00
    不要用递归,展开吧。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5269 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 03:42 · PVG 11:42 · LAX 20:42 · JFK 23:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.