1

Trying to understand Javascript generators and promises, I've checked they are good allies. I need to iterate through a coroutine of promises (Promise.coroutine from Bluebird library) makes easy to execute some promises in the right order. With this code (sorry for the deferred anti-pattern, I'll learn to avoid it later):

function myPromise(x,time,i){
    var deferred = Q.defer();

    setTimeout(() => {
        deferred.resolve(x + i);
    },time);

    return deferred.promise;
}

router.get('/', function(req, res, next) {
    for (var i = 0; i < 5; i++) {      
        Promise.coroutine(function*(i) {
            var a = yield myPromise('a',6000,i);
            var b = yield myPromise('b',1000,i);
            console.log(a,b);
        })(i)
        .then(() => {
            console.log('Then');
        }).
        catch((err) => next(err));
    }
});

The output in console is (almost) right:

a0 b0
a1 b1
a2 b2
Then
Then
Then
a3 b3
a4 b4
Then
Then

Checking this, my for loop seems to be not good because some promises are ending before the others because of the Then.

If I include if(i == 3) deferred.reject(new Error(' ERROR!!')); in the promise, the error is thrown just for that promise and not for the others and it is thrown after the other promises:

ERROR!!
a0 b0
Then
a1 b1
a2 b2
Then
Then
a4 b4
Then

I think iterating with a for loop is not going to be never the solution for this kind of problems. Researching a little bit more, I've tried to use Promise.all with an array of calls to Promise.coroutine:

    Promise
        .all([
            Promise.coroutine(1),
            Promise.coroutine(2),
            Promise.coroutine(3)
        ])
        .then(() => {
            console.log('then');
        })
        .catch((err) => next(err));

But in this case, I take the error:

generatorFunction must be a function

If I do:

var coroutine = function* coroutine(i) {
    var a = yield myPromise('a',6000,i);
    var b = yield myPromise('b',1000,i);
    console.log(a,b);
};

The same generatorFunction must be a function persists.

Do you know what's wrong in here or if there's a better way to "iterate" than Promise.all?

Gustavo Straube
  • 3,744
  • 6
  • 39
  • 62
Joss
  • 535
  • 1
  • 8
  • 28

1 Answers1

2

sorry for the deferred anti-pattern

Actually you didn't use the deferred antipattern in myPromise, using deferreds to get a promise from a callback-taking asynchronous function like setTimeout is totally fine. You could have used the Promise constructor instead, but it's not strictly necessary. The best solution would be simply Promise.delay (Bluebird) or Q.delay (Q) of course :-)

my for loop seems to be not good because some promises are ending before the others

That is because your call to the coroutine (which does the asynchronous things) is inside the loop. If you instead place the loop inside the generator function, it will work as expected:

router.get('/', function(req, res, next) {
    Promise.coroutine(function*() {
        for (var i = 0; i < 5; i++) {      
            var a = yield myPromise('a',6000,i);
            var b = yield myPromise('b',1000,i);
            console.log(a,b);
            console.log('Then');
        }
    })()
    .catch((err) => next(err));
});

or even better:

var run = Promise.coroutine(function*(req, res, next) {
    for (var i = 0; i < 5; i++) {      
        var a = yield myPromise('a',6000,i);
        var b = yield myPromise('b',1000,i);
        console.log(a,b);
        console.log('Then');
    }
});
router.get('/', function(req, res, next) {
    run(req, res, next).catch(next);
});
Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    It is generally discouraged to create coroutines as anonymous functions that get run in every iteration. It is preferable to create them once and then pass them in. – Benjamin Gruenbaum Jul 03 '16 at 12:47
  • @BenjaminGruenbaum: I thought about including that but figured it's not necessary. Added now :-) – Bergi Jul 03 '16 at 15:27