Complete one promise array first, then complete another promise array JavaScript - javascript

I have a problem with promises and have no idea how resolve this:
My idea is to have two methods that are a "dispatcher". In testFetchFaceCharacter() call all promises that I need to resolved first. Need all data from state: {Body:{}, TopA:{}, eyes:{}} When testFetchCharacter() finished, immediately start testFetchTopCharacter(), only if all previous promises executed successfully.
However, at this point (with this code) have a errors. The promises aren't executed "Synchronously". still retrieved "asynchronously". Which "should not happen". Since it "reduce" (from what I read in several articles) avoided that behavior.
const buildCharacter = (state) => {
try {
testFetchFaceCharacter(state);
testFetchTopCharacter(state);
} catch (e) {
console.error(e + "En buildCharacter");
}
const testFetchCharacter = (state) => {
const promises = [
fetchCustom(state.Body.id, state.Body.file),
fetchCustom(state.TopA.id, state.TopA.file),
fetchCustom(state.eyes.id, state.eyes.file),
fetchCustom(state.mouth.id, state.mouth.file),
fetchCustom(state.nose.id, state.nose.file),
fetchCustom(state.eyebrow.id, state.eyebrow.file),
fetchCustom(state.Clothing.id, state.Clothing.file),
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
const testFetchTopCharacter = (state) => {
const promises = [
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
Y try this:
Execute -> Body
Execute -> TopA
Execute -> [eyes, mouth, nose, Clothing, eyebrow] //No matter the order
then
Execute [beard, hat, hair, glass] //not matter the order

First of all, there is a mistake in your code. You need to understand that as soon as you called a function, you triggered a logic that does something, even if you don't listen to the promise right away, the logic is executing.
So what happened, is that you launched all actions in "parallel" when you are doing function calls in the promises array.
Solution A
You need to "postpone" the actual call of a function until the previous function was successful, you can either do it manually, e.g.
const testFetchTopCharacter = async (state) => {
await fetchCustom(state.beard.id, state.beard.file),
await fetchCustom(state.hat.id, state.hat.file),
await fetchCustom(state.hair.id, state.hair.file),
await fetchCustom(state.glass.id, state.glass.file)
}
Solution B
If you want to use reducer you need to use callback in that array, so that when promise is completed you call the next callback in the chain.
const testFetchTopCharacter = (state) => {
const promises = [
() => fetchCustom(state.beard.id, state.beard.file),
() => fetchCustom(state.hat.id, state.hat.file),
() => fetchCustom(state.hair.id, state.hair.file),
() => fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce((promise, callback) => promise.then(callback), Promise.resolve());
}
Solution C
If an order doesn't matter to you just do Promise.all
const testFetchTopCharacter = (state) => {
return Promise.all([
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
]);
}

Related

Handle multiple requests at the same time and update state when one of them is resolved

I have following scenario:
Multiple http requests will be executed:
const sourceOne = http.get(obj1.product_id);
const sourceTwo = http.get(obj2.product_id);
const sourceThree = http.get(obj3.product_id);
const sourceFour = http.get(obj4.product_id);
can i somehow using Promises or Observables to:
Execute all of the requests at the same time
Update state when one of them is resolved, for example:
sourceThree, finish first - update state on the client
sourceFour, complete after sourceThree - update state on the client
All of the possible solutions that i can found are always wait for all of the requests to be completed before updating the state, any ideas?
You could make use of merge to turn multiple observables into a single observable.
merge(sourceOne, sourceTwo, sourceThree, sourceFour).subscribe((data) => updateState(data))
Here is a link to a stackblitz example: https://stackblitz.com/edit/rxjs-cg31qz?devToolsHeight=33&file=index.ts
I think what you are looking for Promise.race, it will populate the first promise has been resolved.
The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
const arr [obj1.product_id, obj2.product_id, obj3.product_id, obj4.product_id]
const result = Promise.race(arr.map(i => http.get(i))
The answer to your question is Promise.race() which fires, everything in then() callback function, after one of the passed promises has been resolved.
const prom1 = new Promise(r => {
setTimeout(() => {
r('prom1')
}, 1200)
})
const prom2 = new Promise(r => {
setTimeout(() => {
r('prom2')
}, 600)
})
const prom3 = new Promise(r => {
setTimeout(() => {
r('prom3')
}, 1000)
})
const prom4 = new Promise(r => {
setTimeout(() => {
r('prom4')
}, 700)
})
Promise.race([prom1, prom2, prom3, prom4]).then(r => {
console.log(r)
})
If you have RxJS installed, observables can do that.
All at once
forkJoin([sourceOne, sourceTwo, sourceThree, sourceFour])
.subscribe(([one, two, three, four]) => {});
Update client when one of them resolves :
merge(sourceOne, sourceTwo, sourceThree, sourceFour)
.subscribe(() => updateUi());

Can I use this implementation of Promise.allSettled before ES2020?

I am working with a broken codebase so I am trying to touch as little as possible. In particular, right now it's using TypeScript and ES6, and I need to launch an array of promises and wait for them all to finish before I move on with the code execution, regardless of if they resolve or reject. So this is the usecase for Promise.allSettled, which is only available in ES2020.
I tried the following implementation:
const myPromiseAllSettled = (promises) => new Promise((resolve, reject) => {
const results = []
const settle = (result) => {
results.push(result)
if (results.length === promises.length) {
(results.every(value => value) ? resolve : reject)(promises)
}
}
promises.forEach(promise => {
promise
.then(() => settle(true))
.catch(() => settle(false))
})
})
but I have only seen my own code when it comes to promises, so I would like to get some feedback on my implementation. Does it do what other developers would expect it to do? Especially when it comes to the arguments passed to resolve/reject; right now I only pass the array of promises and expect the developer to run a .then(promises => promises.forEach(...)) if they are interested in following up on the individual promises.
I also don't know ideally how I would handle the types with TypeScript here, since I am not so experienced with TypeScript as well. (Writing 'any' everywhere doesn't seem cool to me.)
it should look more like this:
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all([...promises].map(p => Promise.resolve(p).then(fulfilled, rejected)));
}
[...promises] to handle cases where promises is iterable but not an array.
Promise.resolve(p) because the passed value may be not a Promise (or thenable).
If you simply want to be notified about the success, you can do something simpler:
const success = await Promise.all(promises).then(() => true, () => false);
Edit: Didn't like the way handled promises may be an iterable but no array. Adding a version that Array#maps over iterables:
function* map(iterable, callback) {
for (const value of iterable) {
yield callback(value);
}
}
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all(
map(
promises,
p => Promise.resolve(p).then(fulfilled, rejected)
)
);
}

Сollect promises results in foreach loop and only after performing setState

I am new to React and Promises.
My purpose is - Collect array of objects from several Search Services and then transfer whole array to the State.
PNP.SearchResults is a Promise
In code below this.setState is performing earlier then array is ready.
How to fix it?
private getSearchResults() {
const allSearchResults = []
this.state.resultSources.forEach(rSource =>{
this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
})
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
Now console.log("state updated") fires earlier then console.log("push").
But I need vice versa
Because of this.props.searchService.search is async function.
You should await result to make sure data return.
private async getSearchResults() {
const allSearchResults = []
for (const rSource of this.state.resultSources) {
await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
}
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
If i understand correctly, you need to first push the search results into allSearchResults array and then setState. Why not use async await and a basic for loop instead of .then. When you use .then, only the code in the .then callback will execute after the promise is resolved but the other code outside it like this.setState, console.log won't wait till you push all the search results.
async func() {
const allSearchResults = [];
for(let i=0; i<this.state.resultSources.length; i+=1){
const item = await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, this.state.resultSources[i].sourceGuid)
allSearchResults.push({Results: item, sourceGuid: this.state.resultSources[i].sourceGuid})
}
this.setState({PrimaryResults2: allSearchResults} as any, () => {console.log("state updated")})
}

Resolving an array of promises in parallel, with error handling

I have a collection of promises or async functions, I need to manipulate slightly and then execute in parallel.
The issue I am having is that I am unable to resolve the promises.
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks).then(results => results);
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
console.log(runTasks())
Following the successful resolution of promises ideally I would also like to handle errors individually for each task.
The problem is that your registerTask function pushes functions onto the tasks array, not Promise objects. If you change the function like this, it should work:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
tasks.push( asyncFunc() ); // This pushes a promise into 'tasks'
};
Your original code ended up with tasks being an array of functions that have not been called yet. When you call Promise.all(tasks) it ended immediately with undefined for each entry in the array because there were no Promise objects to wait on.
As for error handling, the Promise.all will fail with the first rejection or exception that happens. If you want to handle each task's individual error case, then you should do that in the Promise created in registerTask since doing it during the Promise.all is too late. It's hard to give an example since I'm not sure what kind of error handling would be appropriate for tasks, but perhaps something like this:
const registerTask = (name, task) => {
const asyncFunc = async () => {
return { [name]: await task() };
};
const promise = asyncFunc()
.catch(err => {
return handleError(name, err);
});
tasks.push( promise );
};
function handleError(name, err) {
console.log(`Task ${name} had error ${err}`);
// don't re-throw the error if you want all tasks to complete
// return some error object so you can see which tasks failed after they're done
return {error: err, name: name};
}
Promise.all expects an array of promises and you are passing an array of functions that returns a promise. So change your promise Promise.all to receive the promise of each function:
// Some dummy tasks:
const taskA = () => Promise.resolve(500);
const taskB = () => {
return new Promise(resolve => resolve(300));
};
// Push them into an array:
const tasks = [];
const registerTask = (name, task) => {
tasks.push( async () => {
return { [name]: await task() };
});
};
// trying to get the results
const runTasks = () => {
const result = Promise.all(tasks.map(task => task())); // Execute the tasks to receive the promises
return result;
}
// usage
registerTask('taskA', taskA);
registerTask('taskB', taskB);
runTasks().then((res) => {console.log(res)})

best way to stack function calls in componentDidMount

componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.props.syncFirebaseToStore(user);
thenCallThis()
thenCallThis2()
}
}
whats the best way to do the above? basically I want to call 3 functions but only after the previous one has finished executing?
I tried using resolve new Promise but don't think I quite have the syntax right. I would like to chain it with .then() ideally
With promises:
componentDidMount = () => {
return firebase.auth().onAuthStateChanged((user) => {
if (!user) return Promise.reject('No user!')
return this.props.syncFirebaseToStore(user)
.then(thenCallThis)
.then(thenCallThis2)
})
}
Notes:
I've assumed that this.props.syncFirebaseToStore() returns a promise.
Wrapping the arrow function body in curlies kills the implicit return.
If your functions are already returning Promise instances - it's pretty easy to chain them up and attach an error handler, something like this:
componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//Make sure that this call and the rest return Promises
this.props.syncFirebaseToStore(user)
.then(callThis1)
.then(callThis2)
.catch((err) => {console.error(err)})
}
}
Say you want to pass arguments down the chain so that previous call returns something used by the next one, then you would do something like this:
this.props.syncFirebaseToStore(user) //returns result1 when Promise resolved
.then((results1) => {return callThis1(results1)}) //consumes results1, returns results2
.then((results2) => {return callThis2(results2)})
.catch((err) => {console.error(err)})
with async await the above code could be written as such
componentDidMount() {
firebase.auth().onAuthStateChanged(async function(user) => {
if (user) {
await this.props.syncFirebaseToStore(user);
await thenCallThis()
await thenCallThis2()
}
}
}

Categories