JS: Combining multiple independent promises into a single function

60 Views Asked by At

I have the following code appearing in many of my JavaScript files:

import { doA, doB, doC } from 'asyncDoer'

// ... other stuff

let isDoneA = false
let isDoneB = false
let isDoneC = false
doA.then(_ => isDoneA = true)
doB.then(_ => isDoneB = true)
doC.then(_ => isDoneB = true)

// ... other stuff

I want to implement Don't Repeat Yourself and put that block into a single exported function in asyncDoer. Something like doAll().

But no matter how much I think about it, I can't figure it out. Mainly because they are 2+ unrelated promises that resolve independently. One idea I had was to somehow pass isDoneA,B,C as references so the function modifies them on promise resolution. But I heard that modifing the source parameters is a bad idea that makes the code harder to read and mantain.

Can anyone help me in how you would make that block into a more concise, repeatable unit?

1

There are 1 best solutions below

4
Alexander Nenashev On BEST ANSWER

Seems you need Promise.all():

export const isDone = {A: false, B: false, C:false, all: false};
export function doAll(){
  return Promise.all([
    doA.then(_ => isDone.A = true),
    doB.then(_ => isDone.B = true),
    doC.then(_ => isDone.C = true)
    ]).then(_ => isDone.all = true);
}

Another variant:

export function doAll(){
  const isDone = {A: false, B: false, C:false, all: false};
  
  return [isDone, Promise.all([
    doA.then(_ => isDone.A = true),
    doB.then(_ => isDone.B = true),
    doC.then(_ => isDone.C = true)
    ]).then(_ => isDone.all = true);
  ];
}

Usage:

// you can still get the result, if you don't need, just leave isDone only
const [isDone, promise] = doAll();
promise.then(_ => /* do something after all the tasks are complete */ );

It's not clear though how do you use isDone since there's no reactivity/callbacks. A callback version would like this:

export function doAll(cb){
  
  return Promise.all([
    doA.then(_ => cb('A', _)),
    doB.then(_ => cb('B', _)),
    doC.then(_ => cb('B', _))
    ]).then(_ => cb('all'));
}

Usage:

doAll((task, result) => {
  // do something based on the task completed
});

Regarding myself I often use async generators for stuff like this. For a generator you need rather Promise.race():

const delay = result => new Promise(r => setTimeout(() => r(result), Math.random()*1000));

async function* asyncAll(promises){
  promises = Object.entries(promises).map(([key, promise]) => {
    const task = promise.then(result => {
      promises.splice(promises.indexOf(task), 1);
      return [key, result];
    });
    return task;
  })
  while (promises.length) {
      yield Promise.race(promises);
  }
}

function doAll(){
  return asyncAll({
    A: delay('A result'),
    B: delay('B result'),
    C: delay('C result')
  });
}

(async () => {
  
  for await(const [step, result] of doAll()){
    console.log(step, result);
  }
  
})();