0

I'm trying to simplify Promise chains as much as possible by having a function handle an array of functions. They need to be in order, but my method of giving them the proper syntax isn't working, and I'm sure there's a better way to be doing this. Each function in the chain calls the database and needs to trigger the next function when it returns.

// Run each in order.
var Chain = function(chain, cb){
    chain.forEach(function(i){  i = function(r){return new Promise(i)};   });
    chain.reduce(function(cur, next) { {cur.then(next)} }, Promise.resolve()).then(function() { cb() });
}
Chain([

    r=>{ 
        UserData('elle',  i=>{ 
            console.log(i);
            r(i)
        }) 
    },
    r=>{ 
        UserData('tyler', {age:"22"}, i=>{ 
            console.log(i);
            r(i);
        }) 
    },
    r=>{ 
        UserData('nerd',  i=>{ 
            console.log(i);
            r(i) 
        }) 
    },
    r=>{ 
        UserData('jeax',  i=>{ 
            console.log(i);
            r(i) 
        }) 
    }

], function(){

    console.log("Done.");

});

The example below works how I need it, just not using the Chain function that i desire with the simplicity of each item.

var PChain = function(cb){
    // Convert each item.
    return function(){ return new Promise(r=>{cb(r)}) };
}
// The items.
var chain = [
    PChain(r=>{ 
        UserData('elle',  i=>{ 
            console.log(i);r(i)
        }) 
    }),
    PChain(r=>{ 
        UserData('tyler', {age:"22"}, i=>{ 
            console.log(i);r(i);
        }) 
    }),
    PChain(r=>{ 
        UserData('nerd',  
            i=>{ console.log(i);r(i) 
        }) 
    }),
    PChain(r=>{ 
        UserData('jeax',  
            i=>{ console.log(i);r(i) 
        }) 
    })
];
chain.reduce(function(cur, next) { return cur.then(next) }, Promise.resolve()).then(function() {
    //all executed
    console.log("Done.");
});
  • 1
    `UserData` seems strange: it doesn't return promises, nor does it adhere to the regular callback convention of `(err, result)` arguments. – robertklep Jun 17 '16 at 13:12
  • Though I receive a lot of ridicule for it, I use e as the second argument. In this case, I didn't use it because I'm just testing things out. UserData uses MongoDB to return profile information. – Tyler James Jun 17 '16 at 13:51
  • It isn't clear from the code above why it shouldn't be processed in parallel, but let's assume it shouldn't indeed. I believe that Bluebird may offer something that you need, [`mapSeries`](http://bluebirdjs.com/docs/api/promise.mapseries.html) processes promise array in series. The code above is unnecessarily complicated, I cannot suggest how it applies here. – Estus Flask Jun 17 '16 at 15:56

2 Answers2

2

Your mistake is in the Chain function:

chain.forEach(function(i){  i = function(r){return new Promise(i)};   });
chain.reduce(function(cur, next) { {cur.then(next)} }, Promise.resolve()).then(function() { cb() });

Specifically, assigning to i inside a forEach does absolutely nothing. What you actually want is to use map:

chain.map(function(exec){ return function(r){ return new Promise(exec)}; }).reduce(…);

where map returns an array of the functions that you then can reduce over.


Regardless, what you actually should do is to promisify the UserData function:

function user(name, options) {
    return new Promise((resolve, reject) => {
        if (options)
            UserData(name, options, resolve);
        else
            UserData(name, resolve);
    });
}

with which you don't even need a chain but can simply write

user('elle').then(i => { 
    console.log(i);
    return user('tyler', {age:"22"});
}).then(i => { 
    console.log(i);
    return user('nerd');
}).then(i => {
    console.log(i);
    return user('jeax');
}).then(i => {
    console.log(i);
}).then(function() {
   //all executed
   console.log("Done.");
});

You still can use reduce if you have a dynamically-sized array of user names, but you should not use an array of functions.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you so much! This is a much better alternative and exactly what I was looking for :-) +1 for not referring me to some library. – Tyler James Jun 17 '16 at 17:00
1

You should use Promise.all(iterable) :

Promise.all([r1, r2, r3]).then(function(values) { 
  // Do something
});

UPDATE

To respect a specific order, you should check this answer. There is unfortunately no ES6 method able to do that natively.

One of the most used JS promise queuing system (among others, by Angular) is Kris Kowal's Q and it does recommend something similar, so this is not so far from what your wrote.

NOTE

In your code, it seems that you're trying to achieve some sort of batch requests. You have to know that it's really scarce to really need an ordered promises chain in JS.

  1. If you using that for steams (like file contents operations), it's better to use pipes.
  2. If you're using that for batch/filtered API requests :
    • If you control the API : change your API to manage batch/filtered requests
    • If you don't control it : create a middle-ware application managing it
Community
  • 1
  • 1
Ivan Gabriele
  • 6,433
  • 5
  • 39
  • 60
  • 2
    `They need to be in order` – nils Jun 17 '16 at 13:10
  • I edited my answer to give a more concrete explanation. – Ivan Gabriele Jun 17 '16 at 13:35
  • Not to seem rude, but this isn't an answer, it's a suggestion that I'm not doing things to practice. I'm one of those guys that believe if the language lets me do it, its fine. I want what I want. Moving on, .all runs them all at once, not in order. That is why i'm trying to modify each function in the chain with my forEach statement to chain them properly. I've created a function called PChain that does pretty much the same and works, I'll update my question to provide it. – Tyler James Jun 17 '16 at 13:54
  • @TylerJames Did you read all the points of the answer ? The _UPDATE_ part gives you 2 examples of **ordered** promise queues. – Ivan Gabriele Jun 17 '16 at 14:04
  • I saw it, and it points out .reduce, which is what I'm using. I'm not going to use a module I don't have to use, and since my updated code provides proof that what I want does work, just not quite formatted in the way I wish, I think its safe to say I don't need some other module. – Tyler James Jun 17 '16 at 14:07
  • 1
    @TylerJames to be honest: if you want to do things your way, that's fine, but don't ask people to simplify your code and _not_ expect them to suggest much more common techniques. Your method is not how most people would solve the actual problem, as you've already noticed. – robertklep Jun 17 '16 at 15:27