Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does an array of promises work exactly with reduce?

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 ?

like image 498
Alexander Solonik Avatar asked Nov 13 '18 11:11

Alexander Solonik


1 Answers

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)
like image 145
D Lowther Avatar answered Oct 13 '22 10:10

D Lowther