Search code examples
javascriptnode.jspromisees6-promise

Resolve a list of Javascript Promises - in sequence?


My actual code is very complex but i will simplify this as much as i can:

let counter = 0
console.log("time counter: ", counter)

setInterval(() => {
    counter = counter + 1;
    console.log("time counter: ", counter)
}, 1000)

const myPromises =
  [ new Promise((resolve, reject) => setTimeout(() => {
                console.log("reject(1)")
                reject(1)
        }, 5 * 1000)) // after 5 seconds.

    , new Promise(resolve => setTimeout(() => {
                console.log("resolve(2)")
                resolve(2)
        }, 3 * 1000)) // after 3 seconds.

    , new Promise(resolve => setTimeout(() => {
                console.log("resolve(3)")
                resolve(3)
        }, 3 * 1000))   // after 3 seconds.

    , new Promise((resolve, reject) => setTimeout(() => {
                console.log("reject(4)")
                reject(4)
        }, 1 * 1000))   // after 1 second.

  ]

async function testIt(){
    const results = myPromises.map(async promise => {
            return new Promise((resolve) => {
                // no matter what happens with the individual promise itself, we resolve.
                promise
                    .then(ok => {
                        resolve({ wasSuccessful: true, result: ok })
                    })
                    .catch(err => {
                        resolve({ wasSuccessful: false, error: err })
                    })
            })
    })

    // should be no need to catch anything here. use await.
    const results_ = await Promise.all(results)

    console.log("results_: ", results_)
}

testIt()
    .catch(err => console.log("this error isn't supposed to happen error: ", err))

I essentially want the following:

1. start the first promise( myPromises[0] ). Wait 5 seconds. After that reject it.

2. start the next promise( myPromises[1] ). Wait 3 seconds. Resolve it.

At this point we have 8 seconds on the counter.

3. start the next promise( myPromises[2] ). Wait another 3 seconds. Resolve it.

At this point we have 8 + 3 = 11 seconds on the counter.

4. start the next promise ( myPromises[3] ).. Wait for 1 second.. resolve it.

I guess you go the idea.. How to do this now?

Note this is not then().then().then().. im not reducing/accumulating this list as i seen in other questions on this topic. I don't want this to be rejected for any reason.

Instead i want a list of results. Like this:

results_:  [
  { wasSuccessful: false, error: 1 },
  { wasSuccessful: true, result: 2 },
  { wasSuccessful: true, result: 3 },
  { wasSuccessful: false, error: 4 }
]

But note my console.log output .. even if i get the right result, it shows the real execution order:

time counter:  0
time counter:  1
resolve(4)
time counter:  2
resolve(2)
resolve(3)
time counter:  3
time counter:  4
reject(1)
results_:  [
  { wasSuccessful: false, error: 1 },   // note the array ordering is correct. rejected,
  { wasSuccessful: true, result: 2 },    // resolved,
  { wasSuccessful: true, result: 3 },   // resolved,
  { wasSuccessful: false, error: 4 }    // rejected. good.
]
time counter:  5
time counter:  6
time counter:  7

Basically this promises were fired in paralel, and whichever timeout is faster, is resolved faster.

Instead i wanted it to be like this:

time counter:  0
time counter:  1
time counter:  2
time counter:  3
time counter:  4
time counter:  5
reject(1)
time counter:  6
time counter:  7
time counter:  8
resolve(2)
time counter:  9
time counter:  10
time counter:  11
resolve(3)
time counter:  12
resolve(4)
results_:  [
  { wasSuccessful: false, error: 1 },
  { wasSuccessful: true, result: 2 },
  { wasSuccessful: true, result: 3 },
  { wasSuccessful: false, error: 4 }
]
time counter:  13
time counter:  14
...

This is simplification. In practice, what i have list of 30k+ records - on which i need to perform some api action, and essentially resolve a promise. I grouped this list in sublists of 10 elements each. I'm gonna run in parallel each sublist.

But the big list.. aka the lists of lists.. needs sequence:

bigList = [ [ small parallel list 0 ], [ small parallel list 1 ] .. ]

Each promise in this parallel ones is very computationally intensive already. Im lucky if i can run 10 in parallel. So that's why the big list must be sequential. Or else it will fire a tree of promises with 30k leafs which will crash something.

Still not sure if at this scale is realistic but after i'm implementing this sequence i will be able to tell for sure.

So how to run this 4 promises above in sequence? Thanks.


Solution

  • All those promises begin at the same time, so you're running them in parallel

    You could just have the executor function of the promise in the array - then run the executor in a reduce rather than a map

    let counter = 0
    console.log("time counter: ", counter)
    let int = setInterval(() => {
        counter = counter + 1;
        console.log("time counter: ", counter)
    }, 1000)
    setTimeout(() => clearInterval(int), 15000);
    const myPromiseExecutors =
      [ (resolve, reject) => setTimeout(() => {
                    console.log("reject(1)")
                    reject(1)
            }, 5 * 1000) // after 5 seconds.
    
        , resolve => setTimeout(() => {
                    console.log("resolve(2)")
                    resolve(2)
            }, 3 * 1000) // after 3 seconds.
    
        , resolve => setTimeout(() => {
                    console.log("resolve(3)")
                    resolve(3)
            }, 3 * 1000)   // after 3 seconds.
    
        , (resolve, reject) => setTimeout(() => {
                    console.log("reject(4)")
                    reject(4)
            }, 1 * 1000)   // after 1 second.
    
      ]
    
    async function testIt(){
        const results = await myPromiseExecutors.reduce(async (promise, exec) => {
            const ret = await promise;
            try {
                const ok = await new Promise(exec);
                ret.push({ wasSuccessful: true, result: ok });
            } catch(err) {
                ret.push({ wasSuccessful: false, error: err });
            }
            return ret;
        }, Promise.resolve([]))
    
    
        console.log("results_: ", results)
    }
    testIt();

    to be honest, it's probably going to be cleaner to use a for...of loop

    let counter = 0
    console.log("time counter: ", counter)
    let int = setInterval(() => {
        counter = counter + 1;
        console.log("time counter: ", counter)
    }, 1000)
    setTimeout(() => clearInterval(int), 15000);
    const myPromiseExecutors =
      [ (resolve, reject) => setTimeout(() => {
                    console.log("reject(1)")
                    reject(1)
            }, 5 * 1000) // after 5 seconds.
    
        , resolve => setTimeout(() => {
                    console.log("resolve(2)")
                    resolve(2)
            }, 3 * 1000) // after 3 seconds.
    
        , resolve => setTimeout(() => {
                    console.log("resolve(3)")
                    resolve(3)
            }, 3 * 1000)   // after 3 seconds.
    
        , (resolve, reject) => setTimeout(() => {
                    console.log("reject(4)")
                    reject(4)
            }, 1 * 1000)   // after 1 second.
    
      ]
    
    async function testIt(){
        const results = [];
        const p = Promise.resolve();
        for (let exec of myPromiseExecutors) {
            try {
                const ok = await new Promise(exec);
                results.push({ wasSuccessful: true, result: ok });
            } catch(err) {
                results.push({ wasSuccessful: false, error: err });
            }
        }
    
        console.log("results_: ", results)
    }
    testIt();