combining results from multiple rxjs observables - javascript

I have an autocomplete input which, as the user types, fetches data from multiple endpoints, such as:
//service call to fetch data and return as single observable
getAutocompleteSuggestions() {
const subs$ = [
this.http.get(endpoint1),
this.http.get(endpoint2),
this.http.get(endpoint3)
];
return Observable.forkJoin(...subs$);
}
Each of these endpoints returns data of the form:
{ data: [], status: xyz }
I would like to use switchmap as I want to only show results from the final call, and have tried the following:
this.getAutocompleteSuggestions(query)
.switchMap(res => {
return res.data;
})
.subscribe((results: any) => {
this.results = results;
});
But the 'res' in the switchmap is an array, any idea how results can contain a single array containing the data from the response of any number of observables?

I don't fully understand what you want, but I think this is it:
$filter: Subject<string> = new Subject(); //I guess we have some value to filter by??
Push a value to the subject:
this.$filter.next(myNewValue);
In the constructor or init:
this.$filter
.switchMap(filterValue => { //Get the values when filter changes
subs$ = [
this.http.get(endpoint1 + filterValue),
this.http.get(endpoint2 + filterValue),
this.http.get(endpoint3 + filterValue)
];
return Observable.forkJoin(...subs$);
})
.map(results => { //now map you array which contains the results
let finalResult = [];
results.forEach(result => {
finalResult = finalResult.concat(result.data)
})
return final;
})
.subscribe(); //Do with it what you want
The entire steam will be executed again when we put a new value into our subject. SwitchMap will cancel all ready requests if there are any.

I have used something similar to this, and worked well so far:
let endpoint1 = this.http.get(endpoint1);
let endpoint2 = this.http.get(endpoint2);
let endpoint3 = this.http.get(endpoint3);
forkJoin([endpoint1, endpoint2, endpoint3])
.subscribe(
results => {
const endpoint1Result = results[0].map(data => data);
const endpoint2Result = results[1].map(data => data);
const endpoint3Result = results[2].map(data => data);
this.results = [...endpoint1Result, ...endpoint2Result, ...endpoint3Result];
},
error => {
console.error(error);
}
);
Obviously is a pretty simple example, you'll be able to handle better the results to suit your needs.

Related

How to pass array of value to api call and store response in single array using angular

I have array of values, will pass each array value to api call and
I need to know how to save all response in single array in angular
this.result:[]=[];
this.formData=["S1", "S2"]
ngOnInit() {
for(let item of formData) {
this.httpClient.post(this.PartsAPIURL, item, { headers: headers })
.subscribe((data: any) => {
this.result.push(...data);
});
}
}
console.log(result);
Expected Result;
Response all should be stored in single array.
forkJoin will make multiple requests and emit a single array of all responses:
public result: [] = [];
public formData = ['S1', 'S2'];
private requests = this.formData.map(
item => this.httpClient.post(this.PartsAPIURL, item, { headers })
);
private results$ = forkJoin(this.requests);
ngOnInit() {
this.results$.subscribe(
result => this.result = result
);
}

Unable to use async in forEach loop inside an API call

I have a get API call which looks like this
router.get('/review', async (req, res) => {
try {
const entity = await Entity.find();
const entityId = [];
Object.keys(entity).forEach((key) => {
entityId.push(entity[key]._id);
});
const results = [];
Object.keys(entityId).forEach(async (key) => {
const reviews = await Review.find({ entityId: entityId[key] });
results.push(reviews);
});
res.send(results);
} catch (e) {
res.status(500).send();
}
});
In entityId array it has a list of all the id i need and till there it works. Now what I want to do is iterate over each id of entityId and find the corresponding review that the entity has, push those review into a results array and return results.
review has an entityId field which is same as id of entity.
I also looked at - Using async/await with a forEach loop
which suggested to use for loop but it gave the following error.
iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.eslintno-restricted-syntax
How can I solve this?
forEach does not respect await therefore it may result in unintentional behaviour
you can use map to return array of promises from Review.find() and wrap those in await Promise.all({promises array here}) and use await on Promise.all.
This will result in parallel calls instead of doing them sequentially one after another.
const promisesWoAwait = this.entityIds.map(entityId => Review.find({ entityId }));
const result = await Promise.all(promisesWoAwait);
use promises instead of foreach.
Thy this
const data = async () => {
const entity = {
a: { _id: "1231" },
b: { _id: "1232" },
c: { _id: "1233" }
};
const entityId = [];
Object.keys(entity).forEach(key => {
entityId.push(entity[key]._id);
});
const promise = [];
Object.keys(entityId).forEach(async key => {
const reviews = Review({ entityId: entityId[key] });
promise.push(reviews);
});
const results = await Promise.all(promise);
};
const Review = (option) => {
return true;
};
data();

React - Returning data from API

I know there are similar questions, but I can't find the answer.
First, please tell me if I'm doing something really wrong
I need to populate my state with data from an API call. This was working fine with code above:
export const GetPlanets = async () => {
const planets = await axios.get(`${BASE_URL}`).catch((e) => {
console.error(e);
})
return planets.data.results
}
But then I needed to make a second call to several links from one json response filed, and I managed to make it work (don't know if it is the correct approach, though)
const GetPlanets = async () => {
let planetas = {}
await axios.get(`${PLANETS_URL}`)
.then((p) => {
planetas = p.data.results
return axios.get(`${FILMS_URL}`)
}).then((f) => {
planetas.films.forEach((v, i) => {
planetas[i].film = f
})
})
})
.catch((e) => {
console.error(e);
})
return planetas
}
This is my component file, where I try to get the object, like I was doing before
useEffect(() => {
const fetchPlanetas = async () => { // ME TRYING...
const planetas = await GetPlanets()
setPlanetas(planetas)
setToShow(planetas[0])
};
fetchPlanetas()
}, [])
But all I get is undefined
You're getting an array of undefined because .map() needs a return value. In both your .map() callbacks, you are not returning anything.
const results = [1, 2, 3, 4]
const results2 = results.map(elem => {
elem = elem + 1
})
console.log(results2)
But, even if you did return something in your .map() callback, GetFilms(f) is asynchronous, so you would not get the results of GetFilms() mapped into the array as you would expect.
You have a couple of options:
If you have access to the API, send the films data along with the rest of the data when you do your first request.
Use async/await and Promise.all() to get responses.

How can I store the data I get from a Promise back into an object with the same key it came from?

A bit of an ambiguous title, but I'll explain...
I am making a fetch call to 4 different URLs from The Movie Database.
Once these fetch calls retrieve the data, it will then setState and update my initial state. However, I don't want my page to load until all of the data is retrieved, so I am using Promise.all (or attempting to).
My code so far...
state = {
movies: {
trending: {},
topRated: {},
nowPlaying: {},
upcoming: {},
},
};
const allMovieURLs = {
trending: `https://api.themoviedb.org/3/trending/all/day?api_key=${API_KEY}`,
topRated: `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`,
nowPlaying: `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`,
upcoming: `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`
};
//initialize an empty array to Promise.all on
const promiseArr = [];
//loop through movie URLs, call fetch + json()
for (const movies in allMovieURLs) {
//create an object that directly describes the data you get back from the url with a key
const obj = {
[movies]: fetch(allMovieURLs[movies]).then(res => res.json())
};
//push that object into an array so you can use Promise.all
promiseArr.push(obj);
Now what I have here is an array (promiseArr) of objects that have the correct key with the correct Promise stored inside of them.
My next plan was going to be to call Promise.all on them, but I need an array of promises. After I get the actual data back from the URL calls, I wanted to store them back in the object, to the correct corresponding key?
I've been stuck at this part for a couple hours now...any tips would be appreciated!
You can use async/await and Object.entries() to convert a JavaScript plain object to an array of arrays of key, value pairs
(async() => {
for (const [movie, url] of Object.entries(allMovieURLs)) {
try {
allMovieURLs[movie] = await fetch(url).then(res => res.json())
.catch(e => {throw e})
} catch(e) {
console.error(e)
}
}
})()
Don't call then when adding to promise array. Instead call it in promise all
const trending = fetch(trending);
...
Promise.all([trending, topRated, nowplaying, upcoming]).then(([trending, topRated, nowplaying, upcoming]) => {
movies.trending = trending.json();
....
});
You could just also store the promises themselves in another array.
function fetch() {
var time = Math.random() * 1E3;
return new Promise(function (res, rej) {
setTimeout(function () {
res(time);
}, time);
});
}
const API_KEY = "";
const allMovieURLs = {
trending: `https://api.themoviedb.org/3/trending/all/day?api_key=${API_KEY}`,
topRated: `https://api.themoviedb.org/3/movie/top_rated?api_key=${API_KEY}&language=en-US&page=1`,
nowPlaying: `https://api.themoviedb.org/3/movie/now_playing?api_key=${API_KEY}&language=en-US&page=1`,
upcoming: `https://api.themoviedb.org/3/movie/upcoming?api_key=${API_KEY}&language=en-US&page=1`
};
const promiseArr = [];
const promises = [];
for (const movies in allMovieURLs) {
const obj = { [movies]: undefined };
promises.push(fetch(allMovieURLs[movies]).then(res => obj[movies] = res));
promiseArr.push(obj);
}
Promise.all(promises).then(function () {
console.log(promiseArr);
});
If that is not feasible, you can do something like: Promise.all(promiseArr.map(obj => Object.values(obj)[0]))

Recursive Promise.all with a snapshot in firebase

I have the following structure on my firebase database:
I need to get the values of the keys pin. For that I'm working with a recursive function like this:
let pins = [];
const normalize = (snapchot) => {
snapchot.forEach(function(child) {
if(child.val().pin) {
pins.push(Promise.resolve(child.val().pin));
}
else normalize(child);
});
return Promise.all(pins);
}
And now, call the normalize function:
normalize(snapshot) // snapshot represents the data from the firebase db
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})
And it works, but when I debug that code, I see that return Promise.all(pins); gets called more than one time. I only need to be called only once, after the foreach have been completly finished; that's with the idea for the case of performance, because the snapshot data it's more large than the see it in the image I show.
Any ideas ???
to only use Promise.all once you can have the recursive function as a function "inside" `normalize
const normalize = (snapshot) => {
const process = x => {
let ret = [];
x.forEach(function(child) {
if(child.val().pin) {
ret.push(Promise.resolve(child.val().pin));
} else {
ret = ret.concat(process(child));
}
});
return ret;
});
return Promise.all(process(snapshot));
}
This code also doesn't require a global array to store the results
However, as there is nothing asynchronous about any of the code you are calling - dispense with the Promises inside normalize
const normalize = (snapshot) => {
let ret = [];
snapshot.forEach(function(child) {
if(child.val().pin) {
ret.push(child.val().pin);
} else {
ret = ret.concat(normalize(child));
}
});
return ret;
};
If you really have to use Promises for this code, you can simply
Promise.all(normalize(snapshot))
.then(p => {
console.log(p); // [ 'mi-pin-agosto', 'mi-pin-julio' ]
})
.catch(err => {
// handle error
})

Categories

Resources