Loop with native promises;

自古美人都是妖i 提交于 2019-11-29 13:55:51

Any helper function with promise looping I have seen actually made it much worse than what you can do out of the box with recursion.

It is a little nicer with .thenReturn but yeah:

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);
});

See it in jsfiddle http://jsfiddle.net/fd1wc1ra/

This is pretty much exactly equivalent to:

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

If you wanted to do nested loops it is exactly the same:

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);
});
jib

If all you're trying to do is count to 7 with promises, then this will do it:

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); });

Looping with promises is hard, because it's almost impossible not to fall into JavaScript's closures in a loop trap, but it is doable. The above works because it pushes all use of .then() into a sub-function f of the loop (i.e. away from the loop).

A safer solution, that I use, is to forgo loops altogether and seek out patterns like forEach and reduce whenever I can, because they effectively force the sub-function on you:

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

here f is the same function as above. Try it.

Update: In ES6 you can also use for (let i = 0; i < 8; i++) to avoid the "closures in a loop" trap without pushing code into a sub-function f.

PS: The mistake in your example is .then(main(), - it needs to be .then(function() { return new Promise(main()); }, but really, I think you're using the pattern wrong. main() should return a promise, not be wrapped by one.

Try logging err.stack instead of just err when catching promise errors.

In this case, it looks like resolve and reject are not defined within the anonymous function that gets return from main after the initial iteration is complete. I can't totally follow your control flow, but that seems to make sense - after the 7 iterations are complete, there should no longer be any new promises. However, it seems like the code is still trying to run like there are more promises to resolve.

Edit: This is the problem .then(main(),function (err) {. Invoking main on its own will cause resolve and reject inside the anonymous function to be undefined. From the way I read it, main can only be invoked as an argument to the Promise constructor.

I had a similar need and tried the accepted answer, but I was having a problem with the order of operations. Promise.all is the solution.

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));

I've looked around for various solutions too and couldn't find one that satisfied me, so I ended up creating my own. Here it is in case it's useful to someone else:

The idea is to create an array of promise generators and to give this array to a helper function that's going to execute the promises one after another.

In my case the helper function is simply this:

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;
}

Then, for example, to load multiple URLs one after another, the code would be like this:

// 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');
});

The advantage of this approach is that it creates code that's relatively close to a regular for loop, and with minimal indentation.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!