6

I was reading this article HERE, which speaks about how can you use reduce with promises and in the end the following snippet is shown:

const tasks = getTaskArray();
return tasks.reduce((promiseChain, currentTask) => {
    return promiseChain.then(chainResults =>
        currentTask.then(currentResult =>
            [ ...chainResults, currentResult ]
        )
    );
}, Promise.resolve([])).then(arrayOfResults => {
    // Do something with all results
});

So without changing much of the code , i made the following demo:

const tasks = [ fetch('https://jsonplaceholder.typicode.com/todos/1') , 
                fetch('https://jsonplaceholder.typicode.com/todos/2') ,   
                fetch('https://jsonplaceholder.typicode.com/todos/3')  ];


tasks.reduce((promiseChain, currentTask) => {

    console.log(promiseChain);  

    return promiseChain.then(chainResults => {
        return currentTask.then(currentResult =>
            [ ...chainResults, currentResult ]
        )
    });
}, Promise.resolve([])).then(arrayOfResults => {
    // Do something with all results
    console.log(arrayOfResults);
});

But i still don't understand , since reduce is simplistically just a forEach loop and inside reduce we are relying on a promise being returned, what is the guareenty that the reduce function will not just loop though all the elements in the array (in this case an array of promises) , without the then() firing ?

Alexander Solonik
  • 9,838
  • 18
  • 76
  • 174
  • 1
    Its promises all the way down, it is just looping through the reduce elements and the code is structured in such a way that you are creating a large chain of `.then` operations. I can see reasons to do something like this (you need intermediate values to make the next call), but in your example case `Promise.all(tasks).then()` would likely be the better solution. – D Lowther Nov 13 '18 at 12:07
  • 1
    An array of promises doesn't work with `reduce` at all, you'd just use `Promise.all`. You are talking about an array of tasks (*functions* that return promises) however. – Bergi Nov 13 '18 at 12:43
  • 2
    …and indeed, the code you posted here does not work sequentially, it does the three `fetch` requests immediately. – Bergi Nov 13 '18 at 12:47
  • 1
    "*reduce is simplistically just a forEach loop*" - better to look at it the other way round: `forEach` is just a `reduce` loop but ignoring return values. – Bergi Nov 13 '18 at 20:20
  • 1
    You might want to have a look at https://stackoverflow.com/a/30823708/1048572 – Bergi Nov 13 '18 at 20:22
  • @Bergi thanks buddy , will look into that :) – Alexander Solonik Nov 14 '18 at 06:57

1 Answers1

1

Ok, back from reading the article with a more complete answer. This tactic is for async tasks that rely on one another, but aren't always the same. If they were a fixed structure you would do (from the example):

return task1.then(result1 =>
    task2.then(result2 =>
        task3.then(result3 =>
            [ result1, result2, result3 ]
        )
    )
).then(arrayOfResults => {
    // Do something with all results
});

Think of this in terms of doing maybe database work.

return dbOrm.task1.create()
  .then(newRecord =>
    // task 2 requires the id from the new record to operate
    dbOrm.task2.create({task1_id: newRecord.id})
      .then(result2 =>
        // some other async that relies on result2
        task3(result2).then(result3 =>
            [ result1, result2, result3 ]
        )
    )
).then(arrayOfResults => {
    // Do something with all results
});

This is a fixed set of dependencies, they rely on each other to operate and you need the results of all of them to continue.

The example you linked is meant for that kind of serial execution but in a situation with non-fixed dependencies. Reduce is being used to synchronously constructing a chain of async actions that can then resolve on their own, and since the reduce function is returning a resolved promise you can chain another function off the end of it to handle the completed chain.

Reduce is more than a forEach loop even though they both use iteration to move over an array. Reduce also passes the returned result of the previous iteration to the next iteration. You are 'Reducing' your group of tasks down to one completed task; forEach doesn't act to change the array it is operating on (and Map, another iterating array function, returns a modified new array from the old).

so using the example code:

const tasks = getTaskArray();
return tasks.reduce((promiseChain, currentTask) => {
    return promiseChain.then(chainResults =>
        currentTask.then(currentResult =>
            [ ...chainResults, currentResult ]
        )
    );
}, Promise.resolve([])).then(arrayOfResults => {
    // Do something with all results
});

You would get something like this given 2 iterataions:

// 1
Promise.resolve([])
  .then(chainResults => task1().then(task1Result => ([ ...chainResults, task1Result])

// 2
Promise.resolve([])
  .then(chainResults => task1().then(task1Result => ([ ...chainResults, task1Result])
  .then(chainResults => task2().then(task2Result => ([ ...chainResults, task2Result])

// the then after the reduce
Promise.resolve([])
  .then(chainResults => task1().then(task1Result => ([ ...chainResults, task1Result])
  .then(chainResults => task2().then(task2Result => ([ ...chainResults, task2Result])
  .then(arrayOfResults => //do Something)
  .catch(//because you should handle errors)
D Lowther
  • 1,609
  • 1
  • 9
  • 16