【问题标题】:Proper while() loop for bluebird promises (without recursion?)蓝鸟承诺的正确 while() 循环(没有递归?)
【发布时间】:2016-08-26 14:34:22
【问题描述】:

我已经使用 bluebird 学习了两个星期的 Promise。我已经大部分理解了,但是我去解决了一些相关的问题,似乎我的知识已经崩溃了。我正在尝试做这个简单的代码:

var someGlobal = true;

whilePromsie(function() { 
   return someGlobal;
}, function(result) { // possibly even use return value of 1st parm?
 // keep running this promise code
 return new Promise(....).then(....);
});

作为一个具体的例子:

// This is some very contrived functionality, but let's pretend this is 
// doing something external: ajax call, db call, filesystem call, etc.
// Simply return a number between  0-999 after a 0-999 millisecond
// fake delay.
function getNextItem() { 
    return new Promise.delay(Math.random()*1000).then(function() {
        Promise.cast(Math.floor(Math.random() * 1000));
    });
}

promiseWhile(function() {
    // this will never return false in my example so run forever
    return getNextItem() !== false;
}, // how to have result == return value of getNextItem()? 
function(result) {
    result.then(function(x) { 
        // do some work ... 
    }).catch(function(err) { 
        console.warn("A nasty error occured!: ", err);
    });
}).then(function(result) { 
    console.log("The while finally ended!");
});

现在我已经完成了我的作业!有同样的问题,但在这里面向 Q.js:

Correct way to write loops for promise.

但接受的答案,以及其他答案:

  • 面向 Q.js 或 RSVP
  • 针对蓝鸟的唯一答案是使用递归。这些似乎很可能在像我这样的无限循环中导致巨大的堆栈溢出?或者充其量,是非常低效的并且白白地创建一个非常大的堆栈?如果我错了,那好吧!告诉我。
  • 不允许您使用条件的结果。虽然这不是必需的——我只是好奇它是否可能。我正在编写的代码,一个用例需要它,另一个不需要。

现在,一个关于使用此 async() 方法的 RSVP 的答案。真正让我感到困惑的是 bluebird 文档,我什至在存储库中看到了 Promise.async() 调用的代码,但我在最新的 bluebird 副本中没有看到它。它只是在 git 存储库中还是在什么地方?

【问题讨论】:

  • 呃,您链接到的解决方案确实使用了Promise.method,它是针对 Bluebird 而不是 Q?但如果你想要别的东西,看看here
  • 如果你坚持的话,你可以使用 Bluebird 的 Promise.coroutinewhile 循环 :-) 这就是 RSVP.Promise.async 所做的。
  • There's nothing wrong with recursion,毕竟这是异步环境中唯一可行的方法。不确定这是否是回答您的主要问题的有效副本。
  • 我不确定您所说的“不允许您使用条件结果”是什么意思。如果条件为true,则执行你的body,如果为false,则退出循环;你还想用什么作为“结果”?
  • 仅供参考,从异步回调调用您自己的函数不会导致堆栈堆积。调用异步回调时,堆栈已经展开。

标签: javascript promise bluebird


【解决方案1】:

目前还不是 100% 清楚你要做什么,但我会写一个答案,做你提到的以下事情:

  1. 循环直到满足代码中的某些条件
  2. 允许您在循环迭代之间使用延迟
  3. 允许您获取和处理最终结果
  4. 与 Bluebird 一起工作(我将根据 ES6 承诺标准编写代码,该标准将与 Bluebird 或本机承诺一起工作)
  5. 没有堆栈积累

首先,假设您有一个异步函数,它返回一个 Promise,其结果用于确定是否继续循环。

function getNextItem() {
   return new Promise.delay(Math.random()*1000).then(function() {
        return(Math.floor(Math.random() * 1000));
   });
}

现在,你想循环直到返回的值满足某个条件

function processLoop(delay) {
    return new Promise(function(resolve, reject) {
        var results = [];

        function next() {
            getNextItem().then(function(val) {
                // add to result array
                results.push(val);
                if (val < 100) {
                    // found a val < 100, so be done with the loop
                    resolve(results);
                } else {
                    // run another iteration of the loop after delay
                    setTimeout(next, delay);
                }
            }, reject);
        }
        // start first iteration of the loop
        next();
    });
}

processLoop(100).then(function(results) {
   // process results here
}, function(err) {
   // error here
});

如果你想让它更通用,以便传入函数和比较,你可以这样做:

function processLoop(mainFn, compareFn, delay) {
    return new Promise(function(resolve, reject) {
        var results = [];

        function next() {
            mainFn().then(function(val) {
                // add to result array
                results.push(val);
                if (compareFn(val))
                    // found a val < 100, so be done with the loop
                    resolve(results);
                } else {
                    // run another iteration of the loop after delay
                    if (delay) {
                        setTimeout(next, delay);
                    } else {
                        next();
                    }
                }
            }, reject);
        }
        // start first iteration of the loop
        next();
    });
}

processLoop(getNextItem, function(val) {
    return val < 100;
}, 100).then(function(results) {
   // process results here
}, function(err) {
   // error here
});

您对这样的结构的尝试:

return getNextItem() !== false;

无法工作,因为getNextItem() 返回一个始终为!== false 的承诺,因为承诺是一个对象,因此无法工作。如果你想测试一个 Promise,你必须使用 .then() 来获取它的值,并且你必须异步进行比较,所以你不能直接返回这样的值。


注意:虽然这些实现使用调用自身的函数,但这不会导致堆栈堆积,因为它们异步调用自身。这意味着在函数再次调用自身之前堆栈已经完全展开,因此没有堆栈堆积。 .then() 处理程序总是会出现这种情况,因为 Promise 规范要求在堆栈返回“平台代码”之前不调用 .then() 处理程序,这意味着它在调用.then() 处理程序。


在 ES7 中使用 asyncawait

在 ES7 中,您可以使用 async 和 await 来“暂停”循环。这可以使这种类型的迭代代码更简单。这在结构上看起来更像一个典型的同步循环。它使用await 来等待promise,因为函数声明为async,所以它总是返回一个promise:

function delay(t) {
    return new Promise(resolve => {
        setTimeout(resolve, t);
    });
}

async function processLoop(mainFn, compareFn, timeDelay) {
    var results = [];

    // loop until condition is met
    while (true) {
        let val = await mainFn();
        results.push(val);
        if (compareFn(val)) {
            return results;
        } else {
            if (timeDelay) {
                await delay(timeDelay);
            }
        }
    }
}

processLoop(getNextItem, function(val) {
    return val < 100;
}, 100).then(function(results) {
   // process results here
}, function(err) {
   // error here
});

【讨论】:

  • 感谢您添加有关堆栈溢出的评论,我想无论您使用多少递归承诺,几乎可以保证不会达到堆栈内存限制
  • 为 ES7 添加了 async/await 示例。
猜你喜欢
  • 2015-06-05
  • 1970-01-01
  • 2014-11-23
  • 1970-01-01
  • 2015-09-05
  • 1970-01-01
  • 2016-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多