How can I execute array of promises in sequential order?
Asked Answered
A

9

92

I have an array of promises that need to run in sequential order.

var promises = [promise1, promise2, ..., promiseN];

Calling RSVP.all will execute them in parallel:

RSVP.all(promises).then(...); 

But, how can I run them in sequence?

I can manually stack them like this

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

but the problem is that the number of promises varies and array of promises is built dynamically.

Arta answered 20/11, 2013 at 15:38 Comment(2)
from the other answers and downvotes on mine, it seems more people need to read the rsvp README where it explains "The really awesome part comes when you return a promise from the first handler". If you aren't doing this you are really missing out on the expressive power of promises.Bespoke
Similar question but not framework-specific: https://mcmap.net/q/80794/-resolve-promises-one-after-another-i-e-in-sequence/245966Erminiaerminie
S
145

If you already have them in an array then they are already executing. If you have a promise then it's already executing. This is not a concern of promises (I.E they are not like C# Tasks in that regard with .Start() method). .all doesn't execute anything it just returns a promise.

If you have an array of promise returning functions:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

Or values:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});
Stephaniestephannie answered 20/11, 2013 at 19:25 Comment(12)
this is an excellent way to construct a tree of homogeneous promises that don't require arguments. It is exactly equivalent to using a next_promise pointer to build the tree yourself, which you need to do if the set of promises are not homogeneous with respect to arguments etc. It's just that the reduce function is doing the pointer-to-the-current-leaf bit for you. You'll also want to build the tree of yourself if some of your things can happen concurrently. In a tree of promises, branches are sequences and leaves are concurrent.Bespoke
Thank you for your answer. You are correct that creating a promise already means that it is executing so my question was not correctly formed. I ended up solving my problem differently without promises.Arta
Thank you for this. Not being familiar with the details of reduce it took me a while to understand the first cur is the resolved promise. Based on that I managed to make one that executes promises until one of them succeeds. Just adding it here incase someone finds it useful.Inducement
To pass arguments and context - use this demonstration: emberjs.jsbin.com/popeba/2/edit?js,console,output (bind and apply used a lot)Cladophyll
Not quite sure of the use case of sequential executing promises without requiring the response from one to execute the next?Incinerator
@SSHThis Well first of all, wat. Secondly, the previous response is passed to .then, in this example it's just ignored...Stephaniestephannie
Excellent! Also I apply this for own needs: gist.github.com/msangel/d86a1780c6af03fe0ab4bc97361bf627Rafaelof
If any of these promises fail the error will never be rejected and the promise will never resolve...Cristinacristine
If you already have them in an array then they are already executing. - this phrase should be in bold + bigger font. It's crucial to understand.Beetner
I was going to construct something painful and then I found this answer, amazing that it isn't part of Bluebird or the standard library, great little piece of code. Agree with @ducin, by the way, this isn't always clear (not a fault of this answer).Manella
So, the problem that I was trying to solve is the 'ORA-01000' lock-out problem with Node OracleDB driver - if you're trying to move a lot of data at the same time, it can crash the driver. That small bit of code resolved the issue very elegantly. I really wish that I had thought of this myself.Manella
If you already have them in an array then they are already executing Not only put in bold and bigger font. This need to go to the MDN documentation page.Diecious
R
38

With ECMAScript 2017 async functions it would be done like this:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn();
    }
}

You can use BabelJS to use async functions now

Retired answered 3/4, 2017 at 16:25 Comment(1)
This should be the default approach by now (2020). For first time users it might be important to note two things here: 1. Once a promise exists, it's already going. So it's really important that 2. fn1, fn2, fn3 here are functions e.g. () => yourFunctionReturningAPromise() as opposed to just yourFunctionReturningAPromise(). This is also the reason why await fn() is necessary instead just await fn. See more in the official docs. Sorry for posting as a comment but the edit queue is full :)Pissed
H
11

ES7 way in 2017.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

This will execute the given functions sequentially(one by one), not in parallel. The parameter promises is an array of functions, which return Promise.

Plunker example with the above code: http://plnkr.co/edit/UP0rhD?p=preview

Heavyset answered 30/8, 2017 at 2:54 Comment(0)
G
6

Yet another approach is to define a global sequence function on the Promise prototype.

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

Then you can use it anywhere, just like Promise.all()

Example

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

Disclaimer: Be careful editing Prototypes!

Guipure answered 19/11, 2019 at 10:50 Comment(0)
B
5

A second attempt at an answer in which I try to be more explanatory:

First, some requisite background, from the RSVP README:

The really awesome part comes when you return a promise from the first handler...This allows you to flatten out nested callbacks, and is the main feature of promises that prevents "rightward drift" in programs with a lot of asynchronous code.

This is precisely how you make promises sequential, by returning the later promise from the then of the promise that should finish before it.

It is helpful to think of such a set of promises as a tree, where the branches represent sequential processes, and the leaves represent concurrent processes.

The process of building up such a tree of promises is analogous to the very common task of building other sorts of trees: maintain a pointer or reference to where in the tree you are currently adding branches, and iteratively add things.

As @Esailija pointed out in his answer, if you have an array of promise-returning functions that don't take arguments you can use reduce to neatly build the tree for you. If you've ever implemented reduce for yourself, you will understand that what reduce is doing behind the scenes in @Esailija's answer is maintaining a reference to the current promise (cur) and having each promise return the next promise in its then.

If you DON'T have a nice array of homogeneous (with respect to the arguments they take/return) promise returning functions, or if you need a more complicated structure than a simple linear sequence, you can construct the tree of promises yourself by maintaining a reference to the position in the promise tree where you want to add new promises:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

You can build combinations of concurrent and sequential processes by using RSVP.all to add multiple "leaves" to a promise "branch". My downvoted-for-being-too-complicated answer shows an example of that.

You can also use Ember.run.scheduleOnce('afterRender') to ensure that something done in one promise gets rendered before the next promise is fired -- my downvoted-for-being-too-complicated answer also shows an example of that.

Bespoke answered 20/11, 2013 at 22:46 Comment(3)
This is a lot better, however I feel like you're still drifting off topic. This is common to many answers on promises, people don't seem to take the time to read the question, instead they simply comment on some aspect of promises which they personally understand. The original question does not involve parallel execution, not even a little bit, and it does clearly show that simply chaining via then is desired, you've given a lot of extra information which is hiding the answer to the question which was asked.Febrifacient
@DavidMcMullin "....and it does clearly show that simply chaining via then is desired..." but actually he states the sequence of promises are built up dynamically. So he does need to understand how to construct a tree, even if in this case it is the simple subset of tree "linear sequence". You still have to build it by maintaining a reference to the last promise in the chain and adding new promises to it.Bespoke
When OP said the "number of promises varies and array of promises is built dynamically", I'm pretty sure all s/he meant was that the size of the array was not predetermined and that s/he therefore couldn't use a simple Promise.resolve().then(...).then(...)..., not that the array was growing while the promises were executing. Of course, it's all moot now.Vienna
E
2

I had similar problem, and I made a recursive function which runs functions one by one sequentially.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

In case you need to collect output from these functions:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};
Eventuality answered 16/8, 2017 at 21:49 Comment(0)
A
2

All is needed to solve that is a for loop :)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}
Advisee answered 29/8, 2017 at 4:14 Comment(2)
Why does if(!chain) chain = promises[i](); have a () at the end? I think that in the case where the chain is empty (iteration 0) one would just want to have the raw promise, and then the loop can inject each subsequent promise into the chain's .then(). Thus, would this not be if(!chain) chain = promises[i];? Perhaps I have not understood something here.Rora
Ah - your a,b,c are indeed functions returning Promises, and not Promises. So the above makes sense. But what utility is there in wrapping the Promises in this way?Rora
V
0

The thing I was after was essentially mapSeries, and I happen to be mapping save over a set of values, and I want the results.

So, here's as far as I got, to help others searching for similar things in the future..

(Note that the context is an Ember app).

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});
Volatile answered 26/5, 2014 at 7:12 Comment(0)
U
0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

then

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

it is also possible to store what promises return in another private var and pass it to callbacks

Upper answered 20/6, 2020 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.