2
var promiseReturningFuncs = [];
for(var i = 0; i < 5; i++){
  promiseReturningFuncs.push(askQuestion);
}

var programmers = [];
Promise.reduce(promiseReturningFuncs, function(resp, x) {
  console.log(typeof resp);
  if(typeof resp != "function") {
    programmers.push(resp);
  }
  return x();
})
.then(function(resp) {
  programmers.push(resp);
  console.log(programmers);
});

My goal: execute the askQuestion function in series and resolve an array of objects created by that function. (this function must execute in series so that it can respond to user input)

So imagine that the askQuestion function returns a promise that resolves a object I want to add to an array.

This is my messy way of doing it. I am looking to find a cleaner way of doing it, ideally, i wouldn't even need to push to an array, I would just have a final .then, where the response is an array.

Sammy Roberts
  • 657
  • 1
  • 7
  • 15
  • 2
    You might think of this as a [throttling problem](https://stackoverflow.com/q/38385419/1426891) where the number of promises open at a given time is throttled to 1. – Jeff Bowman May 24 '17 at 16:45
  • Promise.all(promiseReturningFuncs.map(func=>func())).then(console.log); – Jonas Wilms May 24 '17 at 16:47
  • Did you try using promise.all()? – error404 May 24 '17 at 16:48
  • If this is working code that you want improved, then this question perhaps belongs in http://codereview.stackexchange.com. If this is not working code, then please describe exactly what output this produces and what output you want it to produce. – jfriend00 May 24 '17 at 16:49
  • 1
    You can see some design patterns for sequencing async operations here: [How to synchronize a sequence of promises](https://stackoverflow.com/questions/29880715/how-to-synchronize-a-sequence-of-promises/29906506#29906506). Also, if you are using the Bluebird promise library (which it looks like you might be), then `Promise.mapSeries()` might be what you want. – jfriend00 May 24 '17 at 16:52
  • @jonas w sadly this wont work as promise.all will still run things in parallel, the issue being askQuestion waits for user input, so it has to run each promise one at a time. I did try this though. – Sammy Roberts May 24 '17 at 16:53

4 Answers4

3

Since you appear to be using the Bluebird promise library, you have a number of built-in options for sequencing your promise returning functions. You can use Promise.reduce(), Promise.map() with a concurrency value of 1, Promise.mapSeries or Promise.each(). If the iterator function returns a promise, all of these will wait for the next iteration until that promise resolves. Which to use depends more upon the mechanics of how your data is structured and what result you want (neither of which you actually show or describe).

Let's suppose you have an array of promise returning functions and you want to call them one at a time, waiting for the one to resolve before calling the next one. If you want all the results, then I'd suggest Promise.mapSeries():

let arrayOfPromiseReturningFunctions = [...];

// call all the promise returning functions in the array, one at a time
// wait for one to resolve before calling the next
Promise.mapSeries(arrayOfPromiseReturningFunctions, function(fn) {
    return fn();
}).then(function(results) {
     // results is an array of resolved results from all the promises
}).catch(function(err) {
     // process error here
});

Promise.reduce() could also be used, but it would accumulate a single result, passing it from one to the next and end with one final result (like Array.prototype.reduce() does).

Promise.map() is a more general version of Promise.mapSeries() that lets you control the concurrency number (the number of async operations in flight at the same time).

Promise.each() will also sequence your functions, but does not accumulate a result. It assumes you either don't have a result or you are accumulating the result out-of-band or via side effects. I tend to not like to use Promise.each() because I don't like side effect programming.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
3

You could solve this in pure JS using ES6 (ES2015) features:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

It applies the function given to the array in series and resolves to an array of the results.

Usage:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

You'll want to double check browser compatibility but this works on reasonably current Chrome (v59), NodeJS (v8.1.2) and probably most others.

Molomby
  • 5,859
  • 2
  • 34
  • 27
0

Executing one after another using a recursive function( in a non promise way):

(function iterate(i,result,callback){
 if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);

For shure this can be wrapped in a promise:

function askFive(){
return new Promise(function(callback){
(function iterate(i,result){
 if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
})(0,[],console.log);
});
}

askFive().then(console.log);

Or:

function afteranother(i,promise){
   return new Promise(function(resolve){
     if(!i) return resolve([]);
     afteranother(i-1,promise).then(val=>promise().then(val2=>resolve(val.concat([val2])));
   });
}

afteranother(5,askQuestion).then(console.log);
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 3
    This executes all the questions in parallel, not in series. I don't think that's what the OP is asking for. – jfriend00 May 24 '17 at 16:50
  • This is in parallel, I need things to be done in series as explained in my question. – Sammy Roberts May 24 '17 at 16:52
  • Why fall back to callbacks? Return the promise you have! – Bergi May 24 '17 at 17:15
  • Sorry but I am trying to avoid recursion, that was my original solution, but yes this would definitely work thank you! – Sammy Roberts May 24 '17 at 18:07
  • @Jonasw You don't need to (and should not) [wrap it in a `new Promise`](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) – Bergi May 24 '17 at 18:21
0

You can use recursion so that you can move to the next iteration in a then block.

function promiseToExecuteAllInOrder(promiseReturningFunctions /* array of functions */) {
  var resolvedValues = [];

  return new Promise(function(resolve, reject) {
    function executeNextFunction() {
      var nextFunction = promiseReturningFunctions.pop();
      if(nextFunction) {
        nextFunction().then(function(result) {
          resolvedValues.push(result);
          executeNextFunction();
        });
      } else {
        resolve(resolvedValues);
      }
    }
    executeNextFunction();
  }
}
alexanderbird
  • 3,847
  • 1
  • 26
  • 35
  • Avoid the [`Promise` constructor antipattern](http://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi May 24 '17 at 17:14
  • Thanks for the link @Bergi, seems like I don't know what's up. Reading http://taoofcode.net/promise-anti-patterns/, it seems like there is lots wrong with my answer. @jfriend00's answer looks much better - assuming `Promise.mapSeries` / `Promise.map` / `Promise.each` are available to the OP – alexanderbird May 24 '17 at 17:30