6

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

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

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

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

4

5 回答 5

7

我见过的任何带有 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);
});
于 2015-01-25T13:54:54.827 回答
2

如果你想要做的只是用承诺数到 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 闭包,但它是可行的。上述工作是因为它将所有使用 .then() 推入循环的子函数f(即远离循环)。

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

[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是与上面相同的功能。试试看

更新: 在 ES6中,您还可以使用for (let i = 0; i < 8; i++)来避免“循环中的闭包”陷阱,而无需将代码推送到子函数中f

PS:你的例子中的错误是.then(main(),- 它需要.then(function() { return new Promise(main()); },但真的,我认为你使用错误的模式。main()应该返回一个承诺,而不是被一个承诺。

于 2015-01-26T05:03:30.740 回答
0

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

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

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

于 2015-01-25T07:53:22.560 回答
0

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

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

在我的情况下,辅助函数就是这样的:

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 循环,并且缩进最少。

于 2017-05-18T16:33:56.893 回答
0

我有类似的需求并尝试了接受的答案,但我遇到了操作顺序的问题。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));
于 2017-05-26T18:44:02.957 回答