Loop with native promises;

主宰稳场 提交于 2020-01-10 04:57:09

问题


I'm trying to make an asynchronous loop with native ES6 promises It kind of works, but incorrectly. I suppose I made a huge mistake somewhere and I need someone to tell me where it is and how it's done correctly

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

Now the output:

/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

The good part: it reaches the end.

The bad part: it catches some errors and I don't know why.


回答1:


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



回答2:


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.




回答3:


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.




回答4:


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



回答5:


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.



来源:https://stackoverflow.com/questions/28134271/loop-with-native-promises

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