20

I have a function from a library that returns a promise. I need to run this function multiple times, but each iteration must wait until the previous task is done.

My assumption was that I could do this:

promiseReturner(1)
  .then(promiseReturner(2)
  .then(promiseReturner(3)
  .then(...)

Which could be simplified using a loop:

var p = Promise.resolve();
for (var i=1; i<=10; i++) {
  p = p.then(promiseReturner(i));
}

However, when I do this each promise in the chain is executed at the same time, instead of one after the other as .then() seems to imply. Clearly I'm missing something fundamental about promises -- but after reading several tutorials and blog posts I'm still lost.

Here's a codepen I wrote up to demonstrate my attempt.

Ben Davis
  • 13,112
  • 10
  • 50
  • 65

6 Answers6

23

Your "non-loop" solution shouldn't work either. You have to pass a function to .then, not a promise:

var p = Promise.resolve();
for (var i=1; i<=10; i++) {
  (function(i) {
      p = p.then(function() {
          return promiseReturner(i);
      });
  }(i));
}

If that function returns a promise, then you get that chaining effect.

More info about promises on MDN.


Can be simplified with let (and arrow functions):

var p = Promise.resolve();
for (let i=1; i<=10; i++) {
    p = p.then(() => promiseReturner(i));
}

Or .bind (which is ES5):

var p = Promise.resolve();
for (var i=1; i<=10; i++) {
    p = p.then(promiseReturner.bind(null, i));
}
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 2
    Maybe more clear: "you have to pass a function *returning a promise*"? – Jos de Jong Feb 23 '15 at 21:03
  • I had tried wrapping it in a function earlier [see here](http://codepen.io/anon/pen/zxjExL?editors=001), but it only seemed to run the last promise. Why does wrapping that block it in a self-invoking function seem to fix it? – Ben Davis Feb 23 '15 at 21:16
  • @BenDavis: [JavaScript closure inside loops – simple practical example](http://stackoverflow.com/q/750486/218196). tl;dr: JavaScript has only function scope, not block scope, so every function references the same `i`. This is fixed with `let` btw. – Felix Kling Feb 23 '15 at 21:18
  • 1
    Ah, thanks! I think I also confused myself, as I thought my promiseReturner function was itself a function, but didn't realize I was passing the result of that function (not the function itself). You'd think someone who's been coding for 15 years would catch that. Go figure. – Ben Davis Feb 23 '15 at 21:22
  • @BenDavis: :) If `promiseReturner` wouldn't need a parameter, you could do `p = p.then(promiseReturner);` (note that I'm not calling the function). But actually, there is an alternative to the IIFE (immediately invoked function expression), `.bind`: `p = p.then(promiseReturner.bind(null, i));`. – Felix Kling Feb 23 '15 at 21:25
  • I had no idea you could do partials like that in js. Brilliant. – Ben Davis Feb 23 '15 at 21:32
2

If you are using es6, you can achieve this using array.reduce. I think quite neatly.

const functions = [/* array of functions which return promises */];
const finalPromise = functions.reduce(async (promise, asyncFn) => {
  await promise;
  return asyncFn();
}, Promise.resolve());
Alastair Brayne
  • 731
  • 1
  • 9
  • 22
1

You can use async/await using es6 generators and a library like co.

co(function* () {
  while(upto < 10) {
    var result = yield Promise.resolve(true);
  }
  return result;
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});

Here's some detail how this works: http://davidwalsh.name/async-generators

eguneys
  • 6,028
  • 7
  • 31
  • 63
1

Here's a solution which I used to solve the same problem:

var recursiveFunction = function(values) {
  return new Promise(function(resolve, reject) {
    if (values.length <= 0) {
        return resolve();
    } else {
        return promiseReturner(values[0]).then(function() {
            values.shift();
            return recursiveFunction(values).then(function() {
              resolve();
            });
        });
      }
  });
}

recursiveFunction([1,2]).then(function(r) {
 console.warn('Finished solving promises sequentially');
})
Bilal Soomro
  • 661
  • 1
  • 7
  • 12
0

Executing promises synchronously with respect to each other can be tricky. I've included an example below that uses Axios promises, but you can replace them with your own. Good luck!

const get = (endpoint = '/', params = {}) => {
  // return axios promise
  return axios({
    method: 'get',
    url: apiHost + endpoint,
    headers: { 'Authorization': 'Token ' + this.state.token },
    params: params,
  });
};

get('/api/some-endpoint/')
  .then((response) => {
    console.log(response);
    //return next promise
    return get('/api/another-endpoint/');
  }).then((response) => {
    console.log(response);
    // return next promise
    return get('/api/yet-endpoint');
  }).then((response) => {
    console.log(response);
    // return next promise
    return get('/api/last-endpoint/');
  }).then((response) => {
    console.log(response);
    // finished, no more promises left in the chain
  })
  .catch(function (error) {
    console.log('Error getting data', error);
  });
Quantyle
  • 11
  • 1
  • 3
-1

You can run your code via nsynjs, it will pause execution on each function that returns promise, and will wait until promise is resolved:

var promiseReturner = function(i) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve("result is "+i)
        }, 1000);
    });
};

function synchronousCode() {
    for (var i=1; i<=10; i++) {
        var p=promiseReturner(i); // nsynjs will pause here until promise is resolved
        console.log(p.data); // `data` will contain result of the promise
    }
};
 
nsynjs.run(synchronousCode, null, function(){
 console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
amaksr
  • 7,555
  • 2
  • 16
  • 17
  • After reading the npm readme page, it sounds like this library is essentially an overly complicated custom implementation of the es6 "await" command. Please correct me if i'm wrong, but I'm really not convinced that writing a custom evaluation engine in javascript for javascript is a good idea. – Phil Feb 18 '19 at 13:32