0

Without the use of any extra libraries (async, bluebird etc) I am trying to implement a function that returns a promise resolves (or rejects) based on an array of functions that return promises as an input parameter... Promise.all(iterable) has very similar functionality to what I am trying to accomplish exepct the promises in the iterable parameter of Promise.all do not execute in sequential order.

I could simply chain these functions together however what if functionListwas a list of unkown length...

I have attemped to conceptually show what I am trying to accomplish below:

function foo() {
  return new Promise((resolve, reject) => {
    setTimeout( () => {
        return resolve();
       }, 200);
  })
}

function bar() {
  return new Promise((resolve, reject) => {
    setTimeout( () => {
        return resolve();
       }, 200);
  })
}

function baz() {
  return new Promise((resolve, reject) => {
    setTimeout( () => {
        return reject();
       }, 100);
  })
}

const functionList = [foo, bar, baz];

function promiseSeries(functionList){
    const results = [];
    return new Promise((resolve, reject) => {
      promiseList.map((promise, i) => {
        return new Promise((_resolve, _reject) => {
          promise()
            .then((result) => {
              results.push(result);
              if (i === promiseList.length - 1) {
                return resolve(results);
              }
               else return _resolve();
            })
            .catch(error => error)
        })
      })
    })
  }

Obviously with running Promise.all on functionList we will see that baz (even though its position is functionList[2] resolves first)

Neither Resolve promises one after another (i.e. in sequence)? or Running promises in small concurrent batches (no more than X at a time) provide answers to this question. I do not want to import a library to handle this single utility function and mostly I am just curious as to what this function would look like.

Community
  • 1
  • 1
Maxwelll
  • 2,174
  • 1
  • 17
  • 22
  • 1
    Without any other libraries, You need to do a recursion for this, since you need to wait for one promise to complete, an example (sorry, can also post it as comment) : const recurse = (promiseList) => { if (promiseList.length > 0) { promiseList[0]().then(v => recurse(promiseList.splice(1, promiseList.length)) ) } } – Max Nov 19 '16 at 05:30
  • Array#reduce is good for this sort of thing – Jaromanda X Nov 19 '16 at 05:42
  • @Max its unclear to me how that works... I also tired invoking that function with an array of valid promises and the promise chain never resolves... perhaps you could post a formal answer / fiddle – Maxwelll Nov 19 '16 at 06:30
  • Super frustrating this is NOT a duplicate of "how do I use bluebird to run promises in a series" – Maxwelll Nov 19 '16 at 06:31
  • 1
    https://jsfiddle.net/jbnhLtvk/2/ should give you the idea. Basically, you need to wait for one promise to complete, and then call recursive function but without the head of the array, so in the end the array will be empty and the function will terminate. – Max Nov 19 '16 at 06:44
  • @MaxwellLasky - indeed it would be seeing as you specifically want a pure solution, no libraries ... maybe [this fiddle](https://jsfiddle.net/xgvhzn9s/) will help you – Jaromanda X Nov 19 '16 at 06:44
  • @Max its not super clear to me how your solution handles a promise rejection... I would expect your recurse function to return a promise in order to handle errors or resolve according to the inputted promise arr – Maxwelll Nov 19 '16 at 06:48
  • 1
    Its updated here, https://jsfiddle.net/jbnhLtvk/4/ – Max Nov 19 '16 at 07:17
  • What about using Promise.all() ? – Oreste Nov 19 '16 at 09:06
  • @Oreste please read the question... Promise.all() does not execute an array of promises in order but rather invokes them all synchronously – Maxwelll Nov 19 '16 at 16:02
  • @thefourtheye pretty ridiculous that you marked this as a duplicate of a question you already answered that is basically completely unrelated – Maxwelll Nov 19 '16 at 16:03
  • 1
    Possible duplicate of [Resolve promises one after another (i.e. in sequence)?](http://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence) – Tamas Hegedus Nov 20 '16 at 04:58
  • 1
    *There is a similar question on stack overflow currently but it relates to this question in the context of an external dependency.* What question would that be? –  Nov 20 '16 at 05:25
  • The question that @TamasHegedus linked to in the comment above is the question I had in mind... (I am not trying to accomplish this task using Q or bluebird) – Maxwelll Nov 20 '16 at 16:22
  • @Max if you post your answer I will mark it as correct seeing as your solution was truly the best answer – Maxwelll Nov 21 '16 at 17:24
  • Yeap, sure will do. – Max Nov 21 '16 at 17:58

4 Answers4

1

This is not more complicated than:

funcs.reduce((prev, cur) => prev.then(cur), Promise.resolve())

A recursive version, which return an array of the resolved values:

function recurse([first, ...last], init) {
  if (!first) return Promise.resolve([]);
  return first(init)
    .then(value => 
      recurse(last, value)
        .then(values => [value, ...values])
    );
}

// Testing function to return a function which
// returns a promise which increments its value.
const x = v => y => Promise.resolve(v + y);
    
recurse([x(1), x(2)], 0).then(console.log);
  • If A and B are functions that return promises I am confused as to how your first example is not dramatically different than using `Promise.all()`... With `Promise.all()` both functions are invoked synchronously (as opposed to B waiting for A to resolve) – Maxwelll Nov 20 '16 at 06:29
  • `const recurse = (promises, results) => { if (promises.length > 0) { return promises[0]() .then((res) => recurse(promises.splice(1, promises.length), results.concat(res))) .catch(err => Promise.reject(err)); } else { return Promise.resolve(results); } }` is the function that ended up serving my needs... – Maxwelll Nov 20 '16 at 18:21
  • What bothers me about both of these answers is that in both cases if the promise returned by any of functions rejects it will silently fail - both of these solutions implicitly assume that every promise returned will be "thenable"... – Maxwelll Nov 21 '16 at 17:18
  • 1
    @Maxwell What do you want to happen? In either case, the end result will a be a promise in rejected state (the reason being the reason for the promise which was rejected), which can be handled via `catch`. I can't think of any more reasonable behavior. So eg `recurse(blah).then(/*all promises fulfilled*/).catch(/*one promise was rejected*/)`. –  Nov 21 '16 at 18:05
0

The simplest way is to just chain them, starting with a null promise:

const promises = [foo, bar, baz];
let result = Promise.resolve();
promises.forEach(promise => result = result.then(() => promise));
return result;

As Ali pointed out, you can use a reduction instead but you need to use functions in the then calls:

return promises.reduce(
    (result, promise) => result.then(() => promise),
    Promise.resolve()
);

You can omit the initial value if you know that promises is not empty.

However if you really want to do things in sequence, you often want to deal with an array of functions that return a promise. For example:

return ids.map(id => () => processId(id))
.reduce((p, fn) => p.then(fn), Promise.resolve());
JohnLock
  • 381
  • 3
  • 5
0

A nice and clean way to achieve this without any extra libraries is using recursion. An example:

const p1 = () => Promise.resolve(10);
const p2 = () => Promise.resolve(20);
const p3 = () => Promise.resolve(30);

const promiseList = [p1, p2, p3];

const sequencePromises = (promises) => {
  const sequence = (promises, results) => {
    if (promises.length > 0) {
      return promises[0]()
        .then(res => sequence(promises.splice(1, promises.length), results.concat(res)))
        .catch(err => Promise.reject(err));
    } else {
      return Promise.resolve(results);
    }
  }
  return sequence(promises, []);
}

sequencePromises(promiseList)
   .then(res => console.log(res))
   .catch(err => console.log("ERROR"));
Max
  • 651
  • 8
  • 16
  • @Maxwell `.catch(err => Promise.reject(err));` is a no-op (that's tech-talk for "doesn't do anything"). –  Nov 21 '16 at 20:42
  • @torazaburo Just remove it then, I didn't notice when I was writing the code. Another way, `catch` is useful is if he wants to continue the promise even if there's one failure. – Max Nov 22 '16 at 03:32
-1
promises.reduce((previous, promise) => {
  if (previous) {
    return previous.then(() => promise);
  }
  return promise;
}, null)
Aji Hao
  • 21
  • 1
  • 6