【问题标题】:Loop with native promises;使用原生承诺循环;
【发布时间】:2015-03-23 22:19:50
【问题描述】:

我正在尝试使用本机 ES6 promises 创建一个异步循环,它有点可以工作,但不正确。我想我在某个地方犯了一个大错误,我需要有人告诉我它在哪里以及它是如何正确完成的

var i = 0;

//creates sample resolver
function payloadGenerator(){
    return function(resolve) {
        setTimeout(function(){
            i++;
            resolve();
        }, 300)
    }
}

// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
    return function(resolve, reject) {
        console.log('i =', i);
        condition ? reject('fin') : resolve();
    }
}

// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
    return function(resolve, reject) {
        return new Promise(payloadGenerator())
            .then(function(){
                return new Promise(controller(i>6))
            })
            .then(main(),function (err) {
                console.log(err);
                resolve(err)
            })
            .catch(function (err) {
                console.log(err , 'caught');
                resolve(err)
            })
    }
}


new Promise(main())
    .catch(function(err){
        console.log('caught', err);
    })
    .then(function(){
        console.log('exit');
        process.exit()
    });

现在输出:

/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit

Process finished with exit code 0

好的部分:它到达了终点。

不好的部分:它捕获了一些错误,我不知道为什么。

【问题讨论】:

  • 不管你使用 promises 的方式使用库是真的奇怪。你在这里的最终目标是什么?你想用 Promise 实现一个“while”吗?
  • .then(main(),function (err) {main() 什么时候在那里?
  • 你忘了告诉我们这段代码应该做什么。
  • @BenjaminGruenbaum 是的,我知道。我是新的承诺,并试图弄清楚它们是如何工作的。是的,这是一个while循环; JLRishe:它应该数到 7 并且不会产生任何错误

标签: javascript node.js ecmascript-6 promise es6-promise


【解决方案1】:

在捕获 Promise 错误时尝试记录 err.stack 而不仅仅是 err

在这种情况下,看起来resolvereject 没有在初始迭代完成后从main 返回的匿名函数中定义。我不能完全遵循你的控制流程,但这似乎是有道理的——在 7 次迭代完成后,应该不再有任何新的承诺。但是,代码似乎仍在尝试运行,好像还有更多的承诺需要解决。

编辑:这是.then(main(),function (err) { 的问题。单独调用main 将导致匿名函数内部的resolvereject 未定义。从我的阅读方式来看,main 只能作为Promise 构造函数的参数调用。

【讨论】:

    【解决方案2】:

    我见过的任何带有 Promise 循环的辅助函数实际上都比开箱即用的递归更糟糕。

    .thenReturn 会更好一些,但是是的:

    function readFile(index) {
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Read file number " + (index +1));
                resolve();
            }, 500);
        });
    }
    
    // The loop initialization
    Promise.resolve(0).then(function loop(i) {
        // The loop check
        if (i < len) {              // The post iteration increment
            return readFile(i).thenReturn(i + 1).then(loop);
        }
    }).then(function() {
        console.log("done");
    }).catch(function(e) {
        console.log("error", e);
    });
    

    在 jsfiddle 中查看 http://jsfiddle.net/fd1wc1ra/

    这几乎完全等同于:

    try {
        for (var i = 0; i < len; ++i) {
            readFile(i);
        }
        console.log("done");
    } catch (e) {
        console.log("error", e);
    }
    

    如果你想做嵌套循环,那是完全一样的:

    http://jsfiddle.net/fd1wc1ra/1/

    function printItem(item) {
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Item " + item);
                resolve();
            }, 500);
        });
    }
    
    var mdArray = [[1,2], [3,4], [5,6]];
    Promise.resolve(0).then(function loop(i) {
        if (i < mdArray.length) {
            var array = mdArray[i];
            return Promise.resolve(0).then(function innerLoop(j) {
                if (j < array.length) {
                    var item = array[j];
                    return printItem(item).thenReturn(j + 1).then(innerLoop);
                }
            }).thenReturn(i + 1).then(loop);
        }
    }).then(function() {
        console.log("done");
    }).catch(function(e) {
        console.log("error", e);
    });
    

    【讨论】:

    • 太棒了,谢谢! thenReturn 的绝妙技巧。一句话:你这里的代码和jsfiddle上的代码不同,一开始我很困惑。 jsfiddle 版本对我有用,因为 iojs 的 promise 实现没有 thenReturn
    • @amdc thenReturn 来自 bluebird,但可以在原生承诺上实现,如 jsifddle 所示。
    • 效果很好!我希望能够同时执行 5 个循环,当其中一个循环完成后,继续进行第 6 个循环,依此类推,直到数组完成。有什么建议?结帐stackoverflow.com/q/36664272/1760313
    【解决方案3】:

    如果你想做的只是用 promise 数到 7,那么就可以了:

    function f(p, i) {
      return p.then(function() {
        return new Promise(function(r) { return setTimeout(r, 300); });
      })
      .then(function() { console.log(i); });
    }
    
    var p = Promise.resolve();
    for (var i = 0; i < 8; i++) {
      p = f(p, i);
    }
    p.then(function() { console.log('fin'); })
     .catch(function(e) { console.log(e.message); });
    

    使用 Promise 循环很难,因为几乎不可能不落入 JavaScript 的 closures in a loop trap,但它是可行的。上面的方法之所以有效,是因为它将 .then() 的所有使用推入循环的子函数f(即远离循环)。

    我使用的一个更安全的解决方案是完全放弃循环,并尽可能寻找forEachreduce 之类的模式,因为它们有效地将子功能强加给您:

    [0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
    .then(function() { console.log('fin'); })
    .catch(function(e) { console.log(e.message); });
    

    这里的f 与上面的功能相同。 Try it.

    更新:In ES6 你也可以使用for (let i = 0; i &lt; 8; i++) 来避免“循环中的闭包”陷阱,而无需将代码推入子函数f

    PS:您的示例中的错误是 .then(main(), - 它必须是 .then(function() { return new Promise(main()); }, 但实际上,我认为您使用的模式错误。 main() 应该返回一个承诺,而不是被一个承诺。

    【讨论】:

    • 这是一个 for 循环 - 我认为 OP 会选择 while,它不适用于 thenable 链接,因为您不知道提前需要多少链接。
    • 也许 OP 意味着条件取决于前面执行的步骤?如果是这样,则需要递归。在任何情况下,使用 while 循环而不是 for 循环都不能解决这个问题,或者意味着那个。我已将 for 循环更改为 while 循环以显示这一点。 OP 在评论中说他试图用承诺数到 7,并要求有人指出他的错误,我希望我做到了。
    • 当然,将 for 循环更改为另一个同步循环结构也无济于事 - 我的意思是,从概念上讲,迭代次数取决于先前的状态。
    • @BenjaminGruenbaum for 循环也很好。知道如何以不同的方式制作它总是很好,感谢您的贡献。
    【解决方案4】:

    我也四处寻找各种解决方案,但找不到让我满意的解决方案,所以我最终创建了自己的解决方案。这是为了以防它对其他人有用:

    这个想法是创建一个promise生成器数组,并将这个数组提供给一个辅助函数,该函数将一个接一个地执行promise。

    在我的例子中,辅助函数就是这样的:

    function promiseChain(chain) {
        let output = new Promise((resolve, reject) => { resolve(); });
        for (let i = 0; i < chain.length; i++) {
            let f = chain[i];
            output = output.then(f);
        }
        return output;
    }
    

    然后,例如,要一个接一个地加载多个 URL,代码是这样的:

    // First build the array of promise generators:
    
    let urls = [......];
    let chain = [];
    for (let i = 0; i < urls.length; i++) {
        chain.push(() => {
            return fetch(urls[i]);
        });
    }
    
    // Then execute the promises one after another:
    
    promiseChain(chain).then(() => {
        console.info('All done');
    });
    

    这种方法的优点是它创建的代码相对接近于常规 for 循环,并且缩进最少。

    【讨论】:

      【解决方案5】:

      我有类似的需求并尝试了接受的答案,但我遇到了操作顺序问题。 Promise.all 是解决方案。

      function work(context) {
        return new Promise((resolve, reject) => {
          operation(context)
            .then(result => resolve(result)
            .catch(err => reject(err));
        });
      }
      
      Promise
        .all(arrayOfContext.map(context => work(context)))
        .then(results => console.log(results))
        .catch(err => console.error(err));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-12-29
        • 1970-01-01
        • 2020-09-04
        • 2019-06-17
        • 2015-12-18
        • 2021-09-07
        • 2018-03-25
        相关资源
        最近更新 更多