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?
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);
}
})();