Passing composite data in rxjs observable chains - javascript

I have a block of code where I'm calling observables in a chain like so:
getData().flatMap(results => {
return callNextDataMethod(results);
}
.flatMap(results2 => {
// next operation and so forth
})
Now, I understand that flatMap will allow me to pass the results of the previous observable to the next one. However what I need is to both do that as well as pass the results on the first. Let's assume that I do some cleanup, validation, etc on the data that comes back in getData and I want that passed to all flatMap calls down the chain. Is there an operator in rxjs that will do this for me?
Thanks

You can use a map operator to combine the argument received by the flatMap projection function with the observable's result:
getData()
.flatMap(data =>
getMoreData(data).map(moreData => ({ data, moreData }))
)
.flatMap(({ data, moreData }) =>
getEvenMoreData(moreData).map(evenMoreData => ({ data, moreData, evenMoreData }))
)
.flatMap(({ data, moreData, evenMoreData }) =>
...

Related

How do i know who called RXJS MergeMap response

So i merge various Observables into one with mergemap, then i subscribe to said merged Observable and i get various responses. But they are anonymous; the response only says "SUCCESS" but i want to know the id that made that response
const requests = from(
this.array.map((e)=>{
return this.eventsService.acceptId(e.id);
})
).pipe(mergeMap( x => x ));
requests.subscribe(
(res)=>{
console.log(res)
}
)
this works as intended but i want to know the id that caused the response inside the subscribe.
the calls need to be parallel, with individual API calls
You can map the ID's in as part of the originoal observables, before merging themall together.
That might look like this:
const requests = merge(
this.array.map(
({id}) => this.eventsService.acceptId(id).pipe(
map(payload => ({payload, id}))
)
)
);
requests.subscribe(
(res) => {
console.log(res)
}
);

Map.size returns 0 and Map.values() returns empty iterator even though the map is populated

I'm fetching objects from an API and populating a map with them before passing them to a child component as props. The problems is although the when I console.log(map) it has the expected object in it - console.log(map.size) is 0. Similarly map.values() returns an empty iterator.
This is just confusing for me. The child rendered perfectly when I was only passing one object to it as props - but now that I'm trying to fetch and pass multiple objects I can't seem to get the data structure to work for me.
To be clear, I'm well aware the the GraphUI component may well not be processing the map props in the correct way. However, I first want to verify that the data is being passed down properly and so far it doesn't seem to be.
So I guess my question is - what's the best data structure to use to pass the multiple objects I'm fetching down as props that can be easily iterated over?
const Graph = ( { geoCodes, dataChoice } ) => {
const [healthWards, setHealthWards] = useState(null);
const url = '...';
useEffect(() => {
const getData = (geoCodes) => {
var map = new Map();
geoCodes.forEach(geoCode => axios.get(url.concat(geoCode))
.then(response => {
map.set(response.data.name, response.data);
})
.catch(error => console.error(`Error: ${error}`))
);
setHealthWards(map);
};
getData(geoCodes);
}, [geoCodes, healthWards]);
return (
<>
{healthWard !== null ?
<GraphUI
healthWards={healthWards}
dataChoice={dataChoice}
/>
: <LoadingSpinner />
}
</>
);
The problem is that you aren't waiting for your promises to resolve and update map before calling setHealthWards(map). A good way around this is to use Promise.all(), which takes an iterable of promises and returns a promise that resolves when all of the promises in the iterable are resolved:
const geoData = new Map();
Promise.all(geoCodes.map(geoCode => {
return axios.get(url.concat(geoCode))
.then(response => geoData.set(response.data.name, response.data))
})).then(() => setHealthWards(geoData);

Is there a way to set state for each iteration of a foreach

I'm working with an API within a React Application and I'm trying to make the API calls come back as one promise.
I'm using the Promise.all() method which is working great.
I'm stuck trying to set the results of two API calls to state with their own name. The promise code is working correctly and I am trying to forEach() or map() over the two sets of data and save them to state with their own name.
I'm sure there is a simple solution but I've been scratching my head for far too long over this!
I've tried searching all the docs for .map and .forEach with no luck!
fetchData(){
this.setState({loading: true})
const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']
let requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => {
return responses
})
.then(responses => Promise.all(responses.map(r => r.json())))
.then(launches => launches.forEach(obj => {
// I need to set both values to state here
}))
.then(() => this.setState({loading: false}))
}
The API call returns two different arrays. I need to set both arrays to State individually with their own name. Is this possible?
If I understand your question correctly, a better approach might be to avoid iteration altogether (ie the use of forEach(), etc). Instead, consider an approach based on "destructuring syntax", seeing you have a known/fixed number of items in the array that is resolved from the prior promise.
You can make use of this syntax in the following way:
/*
The destructing syntax here assigns the first and second element of
the input array to local variables 'responseFromFirstRequest'
and 'responseFromSecondRequest'
*/
.then(([responseFromFirstRequest, responseFromSecondRequest]) => {
// Set different parts of state based on individual responses
// Not suggesting you do this via two calls to setState() but
// am doing so to explicitly illustrate the solution
this.setState({ stateForFirstRequest : responseFromFirstRequest });
this.setState({ stateForSecondRequest : responseFromSecondRequest });
return responses
})
So, integrated into your existing logic it would look like this:
fetchData() {
this.setState({
loading: true
})
const urls = ['https://api.spacexdata.com/v3/launches/past', 'https://api.spacexdata.com/v3/launches']
const requests = urls.map(url => fetch(url));
Promise.all(requests)
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([responseFromFirstRequest, responseFromSecondRequest]) => {
this.setState({ stateForFirstRequest : responseFromFirstRequest });
this.setState({ stateForSecondRequest : responseFromSecondRequest });
return responses
})
.then(() => this.setState({
loading: false
}))
}
If the two arrays won't interfere with each other in the state, is there a problem with just calling setState in each iteration?
.then(launches => launches.forEach(obj => {
this.setState({ [obj.name]: obj });
}))
If you want to minimise the number of updates then you can create an Object from the two arrays and spread that into the state in one call:
.then(launches => this.setState({
...launches.reduce((obj, launch) => {
obj[launch.name] = launch
return obj
}, {})
}))
forEach also provides the index as the second parameter. Wouldn't something like this work?
launches.forEach((obj, idx) => {
if (idx === 0) {
this.setState('first name', obj);
} else if (idx === 1) {
this.setState('second name', obj);
}
})
Also, this portion literally does nothing..
.then(responses => {
return responses
})
and the Promise.all() here also does nothing.
.then(responses => Promise.all(responses.map(r => r.json())))
should be
.then(responses => responses.map(r => r.json()))

Loop over typescript Array asynchronously

I have a typescript array this.products
I need to loop over the elements of the array and for each element send parameters to Angular service which makes an API call and gets an answer to the client as an Observable. However, due to asynchronous nature of Observable, my loop finishes before all of the answer are sent back from the server.
This is my code:
this.products.forEeach((ele, idx) => {
this.myService.getSomeDetails(ele.prop1, ele.prop2).subscribe(result => {
// do something with result
});
});
I need for the loop to advance only after the completion of each observable subscription. How can I implement it? Thanks.
What you are looking for is forkJoin:
https://rxjs-dev.firebaseapp.com/api/index/function/forkJoin
Map your array of items to an array of api call observables, and pass them into forkJoin. This will emit an array of all your resolved api calls.
Quick and dirty example:
forkJoin(this.products.map(i => this.myService.getSomeDetails(ele.prop1, ele.prop2))).subscribe(arrayOfApiCallResults => {
// get results from arrayOfApiCallResults
})
You don't need async/await keywords to make your call in sequence.
import { concat } from 'rxjs';
concat(this.products.map(ele => this.myService.getSomeDetails(ele.prop1, ele.prop2)))
.subscribe(
response => console.log(response),
error => console.log(error),
() => console.log('all calls done')
)
Try this:
let results = await Promise.all(this.products.map(ele =>
this.myService.getSomeDetails(ele.prop1, ele.prop2).toPromise()
));
// put code to process results here
Requests are sent parallel. Rememeber to add async keyword in function definition which use above code. More info here.
This is good way to code it.
from(this.products).pipe(mergeMap(
ele => this.myService.getSomeDetails(ele.prop1, ele.prop2)
)).subscribe(result => {
() => console.log('.'),
e => console.error(e),
() => console.log('Complete')
});

Axios AJAX call in React returning only initial state

I'm trying to make a simple AJAX call using Axios in React, but I can't figure out where I'm going wrong.
Here is what I have so far (within my component):
ComponentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res
this.setState({ users });
});
}
render() {
console.log(this.state.users) // returns the **initialised EMPTY array**
return (
<div>
{this.state.users.map( user => <p>{user.name}</p>)} // returns nothing
</div>
)
}
I am accessing a simple array at the URL given in the .get() method (check it out to see it's structure if necessary). I then chain on the .then() promise and pass the res argument in an arrow function.
Then I assign the users variable to the result, and attempt to set the state as this. So at this point I'm expecting this.state.users to equal the resulting array.
However...
A) When I try console.log(this.state.users), it returns the initialised empty array
B) When I try to render a map of the array, iterating through the name property on each object, again, nothing.
Where am I going wrong?
A part from the typo (as Phi Nguyen noted), a few other things to fix:
1) Axios resolves the promise with a response object. So to get the results you need to do:
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res.data; // Assuming the response body contains the array
this.setState({ users });
});
Check out the docs about the response object here: https://github.com/mzabriskie/axios#response-schema
2) If there's an exception (your request fails) you probably want to handle it instead of swallowing it. So:
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res.data; // Assuming the response body contains the array
this.setState({ users });
})
.catch(err => {
// Handle the error here. E.g. use this.setState() to display an error msg.
})
typo. It should be componentDidMount instead of ComponentDidMount.

Categories

Resources