48

I have created my first deferred object in Node.js using deferred module and it works great when I pass result to next function and trigger resolve and reject.How to chain execution of array of functions when every function returns deferred.promise ? I have like input parameters array of functions and input parameter for first function and every next function get parameter from previous.

It works like f1(100).then(f2).then(f3), but how when I have n number of functions.

PaolaJ.
  • 10,872
  • 22
  • 73
  • 111

7 Answers7

102

Same idea, but you may find it slightly classier or more compact:

funcs.reduce((prev, cur) => prev.then(cur), starting_promise);

If you have no specific starting_promise you want to use, just use Promise.resolve().

  • This solution is fundamentally stronger than @SLaks' solution because of the closure's ability to reference the current executing callback. In the case where you want to "do extra async stuff" within the chain, `funcs[i]` will not help you but `cur` will. – jchook Feb 22 '16 at 05:44
  • @Kumagoro The `prev.then(cur)` is correct, because remember `cur` is a function which returns a promise. –  Oct 30 '16 at 05:22
52

You need to build a promise chain in a loop:

var promise = funcs[0](input);
for (var i = 1; i < funcs.length; i++)
    promise = promise.then(funcs[i]);
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Yes, this is what I call "chaining by assignment" - not an official term but hopefully meaningful. – Beetroot-Beetroot Jan 27 '14 at 03:15
  • How to invoke a callback after the last promise has been resolved? – saraf Mar 31 '16 at 13:24
  • @saraf: Call `.then()` on it, just like any other promise. – SLaks Mar 31 '16 at 14:34
  • Iterating through array with for statement is less classy than using reducing technique. But works! – Popara Jul 15 '16 at 11:55
  • @Popara I just spent an hour trying to wrap my head around doing this same thing via reduction. And I still can't find a way to express it using reduction... If somebody expresses what SLAks did using reduction i'd appreciate it but until then I'll have to stick to this approach. – Dmytro Aug 13 '16 at 19:44
  • Actually, I got it, reduce takes the chain as well as the "identity condition". I kept passing the "identity" promise as the second argument instead of "identity()", so it kept saying that "prev is not a function". Here is my snippet overviewing a job demo which "first get all checkboxes" "then uncheck them one by one" "then check them one by one". http://hastebin.com/ixojikoxem.coffee – Dmytro Aug 13 '16 at 19:51
  • @Dmitry: That parameter is actually just the initial value. – SLaks Aug 14 '16 at 17:30
  • @SLaks yeah but if you don't specify it or specify a promise as the argument instead of a literal, then it won't resolve the initial promise for you(you have to invoke it first) and cause an error which is not obvious to people unaware of what reduce does, and what that second argument means.. Basically the non obvious part is that YOU have to resolve the first step yourself, after which it pipelines nicely. – Dmytro Aug 14 '16 at 17:40
  • 1
    Be careful with `var` here. The current solution might work, but if you are passing a lambda to `then` that uses `i` you are in for a surprise. – 3limin4t0r Aug 10 '20 at 19:29
7

ES7 way in 2017. http://plnkr.co/edit/UP0rhD?p=preview

  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }

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

allenhwkim
  • 27,270
  • 18
  • 89
  • 122
6

Building on @torazaburo, we can also add an 'unhappy path'

funcs.reduce(function(prev, cur) {
  return prev.then(cur).catch(cur().reject);
}, starting_promise); 
Simon H
  • 20,332
  • 14
  • 71
  • 128
5

ES6, allowing for additional arguments:

function chain(callbacks, initial, ...extraArgs) {
 return callbacks.reduce((prev, next) => {
   return prev.then((value) => next(value, ...extraArgs));
 }, Promise.resolve(initial));
}
jchook
  • 6,690
  • 5
  • 38
  • 40
  • Reducing the chain of promises with this technique is really smooth way to think about what is going to happen you your code. – Popara Jul 15 '16 at 11:51
0

For possible empty funcs array:

var promise =  $.promise(function(done) { done(); });
funcs.forEach(function(func) {
  promise = promise.then(func);         
});
Grigory Kislin
  • 16,647
  • 10
  • 125
  • 197
0

If you're using ES7 and need something thenable and similar to Promise.all(), this seems to work.

Promise.resolve(
    (async () => {
        let results = []
        for (let promise of promises) {
            results.push(await promise)
        }
        return results
    })()
)
Ryan King
  • 3,538
  • 12
  • 48
  • 72