Ben Davis
Ben Davis

Reputation: 13800

How do you synchronously resolve a chain of es6 promises?

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.

Upvotes: 20

Views: 14561

Answers (6)

Quantyle
Quantyle

Reputation: 21

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

Upvotes: 0

amaksr
amaksr

Reputation: 7745

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>

Upvotes: -1

Alastair Brayne
Alastair Brayne

Reputation: 731

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

Upvotes: 2

Bilal Soomro
Bilal Soomro

Reputation: 671

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

Upvotes: 1

Felix Kling
Felix Kling

Reputation: 816322

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

Upvotes: 23

eguneys
eguneys

Reputation: 6396

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

Upvotes: 1

Related Questions