Sammy Roberts
Sammy Roberts

Reputation: 657

How to execute promises in series?

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.

Upvotes: 2

Views: 3889

Answers (4)

Molomby
Molomby

Reputation: 6579

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.

Upvotes: 3

Jonas Wilms
Jonas Wilms

Reputation: 138307

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

Upvotes: 0

jfriend00
jfriend00

Reputation: 707486

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.

Upvotes: 3

alexanderbird
alexanderbird

Reputation: 4198

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

Upvotes: 0

Related Questions